Hibernate - 多對多關聯關係對映
【1】單向多對多
如Category:Item=n:n。
在關係資料模型中,是需要一箇中間表Category-Item來維持這種關聯關係的。該表中存放Category_ID和Item_ID。
與 1-n 對映類似,必須為 set 集合元素新增 key 子元素,指定 CATEGORIES_ITEMS 表中參照 CATEGORIES 表的外來鍵為 CATEGORIY_ID。
與 1-n 關聯對映不同的是,建立 n-n 關聯時, 集合中的元素使用 many-to-many。many-to-many 子元素的 class 屬性指定 items 集合中存放的是 Item 物件, column 屬性指定 CATEGORIES_ITEMS 表中參照 ITEMS 表的外來鍵為 ITEM_ID。
如下所示:
<!-- table: 指定中間表 --> <set name="items" table="CATEGORIES_ITEMS"> <key> <column name="C_ID" /> </key> <!-- 使用 many-to-many 指定多對多的關聯關係. column 指定 Set 集合中的持久化類在中間表的外來鍵列的名稱 --> <many-to-many class="Item" column="I_ID"></many-to-many> </set>
Category類如下:
public class Category { private Integer id; private String name; private Set<Item> items = new HashSet<>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<Item> getItems() { return items; } public void setItems(Set<Item> items) { this.items = items; } @Override public String toString() { return "Category [id=" + id + ", name=" + name + ", items=" + items + "]"; } }
Category.hbm.xml如下:
<hibernate-mapping package="com.jane.n2n">
<class name="Category" table="CATEGORIES">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<!-- table: 指定中間表 -->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID" />
</key>
<!-- 使用 many-to-many 指定多對多的關聯關係. column 執行 Set 集合中的持久化類在中間表的外來鍵列的名稱 -->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
Item類如下:
public class Item {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Item.hbm.xml如下:
<hibernate-mapping package="com.jane.n2n">
<class name="Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
</class>
</hibernate-mapping>
【2】程式碼測試
① 單向多對對持久化
程式碼如下:
@Test
public void testSave(){
Category category1 = new Category();
category1.setName("C-AA");
Category category2 = new Category();
category2.setName("C-BB");
Item item1 = new Item();
item1.setName("I-AA");
Item item2 = new Item();
item2.setName("I-BB");
//設定關聯關係
category1.getItems().add(item1);
category1.getItems().add(item2);
category2.getItems().add(item1);
category2.getItems().add(item2);
//執行儲存操作
session.save(category1);
session.save(category2);
session.save(item1);
session.save(item2);
}
測試結果如下:
Hibernate:
create table CATEGORIES (
ID integer not null auto_increment,
NAME varchar(255),
primary key (ID)
) engine=InnoDB
Hibernate:
create table CATEGORIES_ITEMS (
C_ID integer not null,
I_ID integer not null,
primary key (C_ID, I_ID)
) engine=InnoDB
Hibernate:
create table ITEMS (
ID integer not null auto_increment,
NAME varchar(255),
primary key (ID)
) engine=InnoDB
Hibernate:
alter table CATEGORIES_ITEMS
add constraint FKapmdmq8lb9r8dx9xcvr0s3ull
foreign key (I_ID)
references ITEMS (ID)
Hibernate:
alter table CATEGORIES_ITEMS
add constraint FK9x44r4y633v6gw9tyxlhqn3kk
foreign key (C_ID)
references CATEGORIES (ID)
Hibernate:
insert
into
CATEGORIES
(NAME)
values
(?)
Hibernate:
insert
into
CATEGORIES
(NAME)
values
(?)
Hibernate:
insert
into
ITEMS
(NAME)
values
(?)
Hibernate:
insert
into
ITEMS
(NAME)
values
(?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
② 單向多對多獲取操作
程式碼如下:
@Test
public void testGet(){
//懶載入,不會立即獲取Items
Category category = (Category) session.get(Category.class, 1);
System.out.println(category.getName());
//需要連線中間表
Set<Item> items = category.getItems();
System.out.println(items.size());
}
測試結果如下:
Hibernate:
select
category0_.ID as ID1_0_0_,
category0_.NAME as NAME2_0_0_
from
CATEGORIES category0_
where
category0_.ID=?
C-AA
Hibernate:
select
items0_.C_ID as C_ID1_1_0_,
items0_.I_ID as I_ID2_1_0_,
item1_.ID as ID1_4_1_,
item1_.NAME as NAME2_4_1_
from
CATEGORIES_ITEMS items0_
inner join
ITEMS item1_
on items0_.I_ID=item1_.ID //這裡使用了內連線查詢中間表
where
items0_.C_ID=?
2
【3】雙向多對多
如下所示:
Note :
-
雙向 n-n 關聯需要兩端都使用集合屬性,雙向n-n關聯必須使用連線表。
-
集合屬性應增加 key 子元素用以對映外來鍵列, 集合元素裡還應增加many-to-many子元素關聯實體類。
-
在雙向 n-n 關聯的兩邊都需指定連線表的表名及外來鍵列的列名。兩個集合元素 set 的 table 元素的值必須指定,而且必須相同。
set元素的兩個子元素:key 和 many-to-many 都必須指定 column 屬性,其中,key 和 many-to-many 分別指定本持久化類和關聯類在連線表中的外來鍵列名,因此兩邊的 key 與 many-to-many 的column屬性交叉相同。也就是說,一邊的set元素的key的 cloumn值為a,many-to-many 的 column 為b;則另一邊的 set 元素的 key 的 column 值 b,many-to-many的 column 值為 a。
-
對於雙向 n-n 關聯, 必須把其中一端的 inverse 設定為 true(放棄維護關聯關係), 否則兩端都維護關聯關係可能會造成主鍵衝突。
修改Item類如下:
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet<>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
@Override
public String toString() {
return "Item [id=" + id + ", name=" + name + ", categories=" + categories + "]";
}
}
修改Item.hbm.xml如下:
<hibernate-mapping package="com.jane.n2n">
<class name="Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<!-- 這裡設定inverse=true表示放棄維護關聯關係 -->
<set name="categories" table="CATEGORIES_ITEMS" inverse="true">
<key column="I_ID"></key>
<many-to-many class="Category" column="C_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
【4】雙向多對多測試
雙向多對多持久化
程式碼如下:
@Test
public void testSave(){
Category category1 = new Category();
category1.setName("C-AA");
Category category2 = new Category();
category2.setName("C-BB");
Item item1 = new Item();
item1.setName("I-AA");
Item item2 = new Item();
item2.setName("I-BB");
//設定關聯關係
category1.getItems().add(item1);
category1.getItems().add(item2);
category2.getItems().add(item1);
category2.getItems().add(item2);
item1.getCategories().add(category1);
item1.getCategories().add(category2);
item2.getCategories().add(category1);
item2.getCategories().add(category2);
//執行儲存操作
session.save(category1);
session.save(category2);
session.save(item1);
session.save(item2);
}
測試結果如下:
Hibernate:
create table CATEGORIES (
ID integer not null auto_increment,
NAME varchar(255),
primary key (ID)
) engine=InnoDB
Hibernate:
create table CATEGORIES_ITEMS (
C_ID integer not null,
I_ID integer not null,
primary key (C_ID, I_ID)
) engine=InnoDB
Hibernate:
create table ITEMS (
ID integer not null auto_increment,
NAME varchar(255),
primary key (ID)
) engine=InnoDB
Hibernate:
alter table CATEGORIES_ITEMS
add constraint FKapmdmq8lb9r8dx9xcvr0s3ull
foreign key (I_ID)
references ITEMS (ID)
Hibernate:
alter table CATEGORIES_ITEMS
add constraint FK9x44r4y633v6gw9tyxlhqn3kk
foreign key (C_ID)
references CATEGORIES (ID)
Hibernate:
insert
into
CATEGORIES
(NAME)
values
(?)
Hibernate:
insert
into
CATEGORIES
(NAME)
values
(?)
Hibernate:
insert
into
ITEMS
(NAME)
values
(?)
Hibernate:
insert
into
ITEMS
(NAME)
values
(?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
Hibernate:
insert
into
CATEGORIES_ITEMS
(C_ID, I_ID)
values
(?, ?)
可以看到測試結果和【2】中單向多對多一致,不過需要注意這裡Item放棄了維護關聯關係,否則將會出現主鍵衝突異常!
至於獲取測試,則和【2】中一樣,只不過這裡還可以從Item一端獲取關聯的Category(當然,如果使用本地SQL,【2】中也可以從Item一端獲取關聯的Category)。
參考博文:雙向多對多註解版