1. 程式人生 > >Hibernate - 多對多關聯關係對映

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)。

參考博文:雙向多對多註解版