n-n(多對多)的關聯關系必須通過連接配接表實作。下面以商品種類和商品之間的關系,即一個商品種類下面可以有多種商品,一種商品又可以屬于多個商品種類,分别介紹單向的n-n關聯關系和雙向的n-n關聯關系。
單向的n-n關聯關系
如果僅使用兩張資料表,是不能實作n-n的關聯關系的,如下圖:

商品ITEM_AA屬于商品種類CATEGORY_AA,但是如果商品ITEM_AA又同時屬于商品種類CATEGORY_BB呢,兩張資料表無法實作這種關系,是以我們需要使用到連接配接表,如下圖所示:
我們添加一張連接配接表,其中的每一條記錄表示某一個商品和某一個商品種類的對應關系。
單向的n-n關聯關系的域模型和關系資料模型如下圖所示:
下面來實作這種關系,首先建立兩個類:
public class Category {
private Integer id;
private String name;
private Set<Item> items = new HashSet<>();
//getters and setters
}
public class Item {
private Integer id;
private String name;
//getters and setters
}
生成并編輯映射檔案:
Category.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.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.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.n2n.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>
與1-n關聯關系類似,在映射時,必須在category類的映射檔案中設定set節點。但是不同的是,在set節點中指定的表為連接配接表CATEGORIES_ITEMS,而不再是所關聯的類Item所對應的表,而且,在set節點中設定的是many-to-many節點,而不再是one-to-many。many-to-many節點的class屬性指定了items集合中存放的對象類型是Item,colume屬性指定了連接配接表CATEGORIES_ITEMS中參照ITEMS表的外鍵是ITEM_ID。
現在随便一個測試程式,發現除了生成了表CATEGORIES和表ITEMS,還生成了連接配接表CATEGORIES_ITEMS,表結構為:
下面測試save操作和get操作:
單向n-n關聯關系的save操作:
@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);
}
運作程式,根據控制台列印的sql語句,發現會先往CATEGORIES表和ITEMS表分别插入兩條記錄,然後再往連接配接表CATEGORIES_ITEMS插入四條記錄,這是符合我們的預期的:
單向n-n關聯關系的get操作:
@Test
public void testGet(){
Category category = (Category) session.get(Category.class, );
System.out.println(category.getName());
//需要連接配接中間表
Set<Item> items = category.getItems();
System.out.println(items.size());
}
同之前的1-n、1-1中類似,先查詢category會使用懶加載機制,不會立即加載items,隻有使用到items的時候才會加載。當加載items的時候,是通過内連接配接連接配接中間表CATEGORIES_ITEMS進行查詢的:
雙向的n-n關聯關系
在雙向的n-n關聯關系中,兩端的類都需要使用集合屬性,即Category中要包含一個Set<Item>,Item中也要包含一個Set<Category>,,且在資料庫中同樣需要使用連接配接表連接配接。其域模型和關系資料模型如下:
下面編寫代碼,首先建立兩個類:
public class Category {
private Integer id;
private String name;
private Set<Item> items = new HashSet<>();
//getters and setters
}
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet<>();
//getters and setters
}
生成并編輯映射檔案:
Category.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.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.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.n2n.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>
<set name="categories" table="CATEGORIES_ITEMS" inverse="true">
<key column="I_ID"></key>
<many-to-many class="com.atguigu.hibernate.n2n.Category" column="C_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
大部門内容和單向n-n中類似,隻是需要在Item類中添加字段
private Set<Category> categories = new HashSet<>();
并且需要在Item類的映射檔案中像Category映射檔案中那樣配置set節點。此外,還有一個很重要的地方,就是需要在Category和Item的其中一端的映射檔案中的set節點中設定inverse=”true”屬性,以放棄維護關聯關系,否則兩端都會維護預設關系,這在某些情況下會導緻錯誤,下面會示範。
現在生成資料表,表結構和單向n-n中一樣:
下面測試save操作和get操作:
雙向n-n關聯關系的save操作:
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);
運作程式,和單向n-n中一樣,一共列印8條insert語句,并成功插入記錄。但是,現在我們移除Item.hbm.xml檔案中的inverse=”true”屬性,再運作程式,會抛出異常。這是因為在預設情況下n-n的兩端都會維護關聯關系,當執行上述四條save代碼後,category要維護關聯關系,往連接配接表中添加4條記錄,然後item也要維護關聯關系,往連接配接表中添加相同的4條記錄,會導緻連接配接表中主鍵重複,解決方法就是在Item.hbm.xml檔案中設定inverse=”true”屬性。
雙向n-n關聯關系的get操作:
@Test
public void testGet(){
Category category = (Category) session.get(Category.class, );
System.out.println(category.getName());
//需要連接配接中間表
Set<Item> items = category.getItems();
System.out.println(items.size());
}
雙向n-n關聯關系的get操作和單向n-n中一樣,包含懶加載機制和内連接配接。
補充一下,雙向n-n中兩端的映射檔案的字段對應如下圖所示: