Hibernate的關聯映射

單向N-1關聯 <many-to-one>

單向N-1關係,比如多個人對應同一個住址,只需要從人實體端找到對應的住址實體,無須關係某個地址的全部住戶。程序在N的一端增加一個屬性,該屬性引用1的一端的關聯實體。

例如下面person實體中的address屬性,

 1 package map.six11;
 2 
 3 public class Person {
 4     public Integer getId() {
 5         return id;
 6     }
 7     public void setId(Integer id) {
 8         this.id = id;
 9     }
10     public int getAge() {
11         return age;
12     }
13     public void setAge(int age) {
14         this.age = age;
15     }
16     public String getName() {
17         return name;
18     }
19     public void setName(String name) {
20         this.name = name;
21     }
22     public Address getAddress() {
23         return address;
24     }
25     public void setAddress(Address address) {
26         this.address = address;
27     }
28 
29     private Integer id;
30     private int age;
31     private String name;
32     private Address address; 33     
34 }

Address是一個獨立的實體,

 1 package map.six11;
 2 
 3 public class Address {
 4     private Integer addressId;
 5     private String addressDetail;
 6     public Integer getAddressId() {
 7         return addressId;
 8     }
 9     public void setAddressId(Integer addressId) {
10         this.addressId = addressId;
11     }
12     public String getAddressDetail() {
13         return addressDetail;
14     }
15     public void setAddressDetail(String addressDetail) {
16         this.addressDetail = addressDetail;
17     }
18     public Address() {}
19     public Address(String addressDetail) {
20         this.addressDetail = addressDetail;
21     }
22 }

在N的一端person實體的映射文件中,用<many-to-one>標籤標識關聯的屬性實體address。

值得注意的是 cascade="all" 這個屬性,當實體類有數據更新時,關聯的屬性類也會更新到數據庫,這叫級聯行爲。

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12            <many-to-one name="address" cascade="all" class="Address" column="address_id" />
13     </class>
14 </hibernate-mapping>

 

在1的一端,則是一個普通的映射文件,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail"  />
11     </class>
12 </hibernate-mapping>

 

下面是一個測試類,

 1 package map.six11;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 
 8 public class PersonManager {
 9     public static void testPerson() {
10         Configuration conf = new Configuration().configure();
11         //conf.addResource("map.six11/Person.hbm.xml");
12         conf.addClass(Person.class);
13         //conf.addResource("map.six11/Address.hbm.xml");
14         conf.addClass(Address.class);
15         SessionFactory sf = conf.buildSessionFactory();
16         Session sess = sf.openSession();
17         Transaction tx = sess.beginTransaction();
18         
19         Person p = new Person();
20         Address a = new Address("廣州天河");
21         p.setName("天王蓋地虎");
22         p.setAge(20);
23         p.setAddress(a);
24         sess.persist(p);
25         Address a2 = new Address("上海虹口");
26         p.setAddress(a2);
27         
28         tx.commit();
29         sess.close();
30         sf.close();
31     }
32     
33     public static void main(String[] args) {
34         testPerson();
35     }
36 }

 

測試類中,Address只是一個瞬態的持久類,從未持久化,但是因爲在Person實體類的映射文件中設置了cascade="all"屬性,因此Address實體也會隨着Person實體的更新而發生級聯更新。

因此可以看到Hibernate不僅在Person表中插入了記錄,而且還創建了Address表並且也插入了記錄,hibernate日誌如下,

1 Hibernate: insert into address_inf (addressDetail) values (?)
2 Hibernate: insert into person_inf (age, name, address_id) values (?, ?, ?)
3 Hibernate: insert into address_inf (addressDetail) values (?)
4 Hibernate: update person_inf set age=?, name=?, address_id=? where person_id=?

mysql數據如下,

MariaDB [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| address_inf    |
| person_inf     |
+----------------+
2 rows in set (0.00 sec)

 

表數據

MariaDB [test]> select * from person_inf;
+-----------+------+------------+------------+
| person_id | age  | name       | address_id |
+-----------+------+------------+------------+
|         1 |   20 | 天王蓋地虎 |          2 |
+-----------+------+------------+------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+
| address_id | addressDetail |
+------------+---------------+
|          1 | 廣州天河      |
|          2 | 上海虹口      |
+------------+---------------+
2 rows in set (0.00 sec)

 

表結構如下,person_inf表使用外鍵address_id與address_inf表關聯,形成N-1的關聯關係,address_inf表成爲了主表。

MariaDB [test]> desc person_inf;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| person_id  | int(11)      | NO   | PRI | NULL    | auto_increment |
| age        | int(11)      | YES  |     | NULL    |                |
| name       | varchar(255) | YES  |     | NULL    |                |
| address_id | int(11)      | YES  | MUL | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

MariaDB [test]> desc address_inf;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| address_id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| addressDetail | varchar(255) | YES  |     | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

 

有連接表的N-1關聯

連接表

對於上面的person和address兩個表的關聯有兩種實現方式,一種是像上面那樣在person表中加入一個外鍵字段address_id.

第二種方式則是單獨用一張連接表來存放person和address的映射關係,例如person_address(person_id,address_id)表,只需要保證person_id 字段不重複,即可實現person和address之間N-1的關聯。

如果用Hibernate來實現連接表的N-1關聯,只需要修改person實體類的映射文件,用 <join table="person_address"> 來表示連接表,

表中有兩個字段,person_id用來關聯person表,同時將它作爲連接表的主鍵,用<key>子標籤標識,這樣能保證person_id在連接表中不會重複。另一個字段addressDetail用來關聯address表,同樣用<many-to-one>標籤來表示這個字段,說明person和address之間的N-1關聯關係。

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <join table="person_address">
13             <key column="person_id" />
14             <many-to-one name="address" cascade="all" class="Address"
15                 column="address_id" />
16         </join>
17 
18     </class>
19 </hibernate-mapping>

 

其他代碼不需要做任何修改,執行測試類,發現Hibernate生成了三個表,person_inf和address_inf是兩張相對獨立的表,person_address則將它們關聯起來。

Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: insert into person_inf (age, name) values (?, ?)
Hibernate: insert into person_address (address_id, person_id) values (?, ?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: update person_address set address_id=? where person_id=?

 

MariaDB [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| address_inf    |
| person_address |
| person_inf     |
+----------------+
3 rows in set (0.00 sec)

 

表數據,

MariaDB [test]> select * from person_inf;
+-----------+------+------------+
| person_id | age  | name       |
+-----------+------+------------+
|         1 |   20 | 天王蓋地虎 |
+-----------+------+------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+
| address_id | addressDetail |
+------------+---------------+
|          1 | 廣州天河      |
|          2 | 上海虹口      |
+------------+---------------+
2 rows in set (0.00 sec)

MariaDB [test]> select * from person_address;
+-----------+------------+
| person_id | address_id |
+-----------+------------+
|         1 |          2 |
+-----------+------------+
1 row in set (0.00 sec)

 查看person_address表結構,發現person_id成爲了主鍵(即不可重複),

MariaDB [test]> desc person_address;
+------------+---------+------+-----+---------+-------+
| Field      | Type    | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| person_id  | int(11) | NO   | PRI | NULL    |       |
| address_id | int(11) | YES  | MUL | NULL    |       |
+------------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)

 基於外鍵的單向1-1關聯

單向1-1關聯與上面的單向N-1關聯非常類似,在Hibernate中的實現也非常相似,只需要將上面的單向N-1關聯的例子中,在映射文件<many-to-one>標籤中假如 unique="true"屬性即可,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <many-to-one name="address" cascade="all" class="Address" unique="true" column="address_id" />
13     </class>
14 </hibernate-mapping>

 

其他地方不再需要任何修改,執行測試類,會發現得到的表跟之前一模一樣,

Hibernate日誌

Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: insert into person_inf (age, name, address_id) values (?, ?, ?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: update person_inf set age=?, name=?, address_id=? where person_id=?

 

表數據,

MariaDB [test]> select * from person_inf;
+-----------+------+------------+------------+
| person_id | age  | name       | address_id |
+-----------+------+------------+------------+
|         1 |   20 | 天王蓋地虎 |          2 |
+-----------+------+------------+------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+
| address_id | addressDetail |
+------------+---------------+
|          1 | 廣州天河      |
|          2 | 上海虹口      |
+------------+---------------+
2 rows in set (0.00 sec)

唯一不同的是,查看 person_inf表結構,發現其外鍵 address_id多了一個唯一約束,

MariaDB [test]> desc person_inf;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| person_id  | int(11)      | NO   | PRI | NULL    | auto_increment |
| age        | int(11)      | YES  |     | NULL    |                |
| name       | varchar(255) | YES  |     | NULL    |                |
| address_id | int(11)      | YES  | UNI | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

有連接表的單向1-1

同樣的,只需要像上面那樣,在映射文件中加入 unique="true"屬性即可,只不過這回是有連接表,用<join>標籤而已。

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12                 <join table="person_address">
13             <key column="person_id" />
14             <many-to-one name="address" cascade="all" class="Address" unique="true" 
15                 column="address_id" />
16         </join>
17     </class>
18 </hibernate-mapping>

 其他不用做修改,執行測試類,其結果與前面的基於連接表的N-1關聯一樣,區別是表結構不同,在連接表person_address的addressDetail字段上加了唯一約束,

MariaDB [test]> desc person_address;
+------------+---------+------+-----+---------+-------+
| Field      | Type    | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| person_id  | int(11) | NO   | PRI | NULL    |       |
| address_id | int(11) | YES  | UNI | NULL    |       |
+------------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)

基於主鍵的單向1-1  <one-to-one>

前面有基於外鍵的單向1-1,是在person表中增加外鍵。而基於主鍵的單向1-1則是直接讓peson表的主鍵由address表生成,讓他們的主鍵保持一致,使person表成爲從表。

這種情況下,在person的映射文件中,需要修改主鍵生成策略,由原來的identity策略改成foreign策略,並且添name參數來指定關聯的實體類,

而關聯的address屬性,則需要用<one-to-one>來映射,

<?xml version="1.0"  encoding="UTF-8"?>    
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="map.six11">
    <class name="Person" table="person_inf">
        <id name="id" column="person_id" type="int">
            <!-- 基於主鍵關聯時,主鍵生成策略是foreign,表明根據關聯類的主鍵來生成該實體類的主鍵 -->
            <generator class="foreign" >    
                <!-- 指定引用關聯實體的屬性名 -->
                <param name="property">address</param>
            </generator>
        </id>
        <property name="age" type="int" />
        <property name="name" type="string" />
        <one-to-one name="address"/>
    </class>
</hibernate-mapping>

 

其他地方都不需要修改,再次執行測試類,發現person表和address表的主鍵值是一樣的,

MariaDB [test]> select * from address_inf;
+------------+---------------+
| address_id | addressDetail |
+------------+---------------+
|          1 | 廣州天河      |
+------------+---------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from person_inf;
+-----------+------+------------+
| person_id | age  | name       |
+-----------+------+------------+
|         1 |   20 | 天王蓋地虎 |
+-----------+------+------------+
1 row in set (0.00 sec)

 

無連接表的單向1-N關聯

單向1-N關聯中,1的一端的實體類需要添加集合屬性,而N的一端正是一個集合。

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 public class Person {
 7     public Integer getId() {
 8         return id;
 9     }
10     public void setId(Integer id) {
11         this.id = id;
12     }
13     public int getAge() {
14         return age;
15     }
16     public void setAge(int age) {
17         this.age = age;
18     }
19     public String getName() {
20         return name;
21     }
22     public void setName(String name) {
23         this.name = name;
24     }
25     public Set<Address> getAddresses() {
26         return addresses;
27     }
28     public void setAddresses(Set<Address> addresses) {
29         this.addresses = addresses;
30     }
31 
32 
33     private Integer id;
34     private int age;
35     private String name;
36     private Set<Address> addresses = new HashSet<>(); 37     
38 }

 

在1的一端實體類的映射文件中,使用<set> <list> <map>等標籤來標識關聯的集合屬性,用子標籤<one-to-many>來表示N的一端的實體類。

1的一端與N的一端兩個實體類通過1的一端實體類的主鍵來關聯,即在N的一端數據表address_inf中添加外鍵person_id,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <set name="addresses" table="address_inf">
13             <key column="person_id"/>
14             <one-to-many class="Address" />
15         </set>
16     </class>
17 </hibernate-mapping>

 

這個例子中,因爲只需要從1的一端訪問N的一端,因此N的一端不需要做改變,實體類Address和映射文件都不需要改變。(當然在底層,hibernate會修改address_inf表,添加一個外鍵來關聯person_inf).

 1 package map.six11;
 2 
 3 public class Address {
 4     private Integer addressId;
 5     private String addressDetail;
 6     public Integer getAddressId() {
 7         return addressId;
 8     }
 9     public void setAddressId(Integer addressId) {
10         this.addressId = addressId;
11     }
12     public String getAddressDetail() {
13         return addressDetail;
14     }
15     public void setAddressDetail(String addressDetail) {
16         this.addressDetail = addressDetail;
17     }
18     public Address() {}
19     public Address(String addressDetail) {
20         this.addressDetail = addressDetail;
21     }
22 }

 

Address實體映射文件,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail"  />
11     </class>
12 </hibernate-mapping>

 

測試類如下,

 1 package map.six11;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 
 8 public class PersonManager {
 9     public static void main(String[] args) {
10         Configuration conf = new Configuration().configure();
11         conf.addClass(Person.class);
12         conf.addClass(Address.class);
13         SessionFactory sf = conf.buildSessionFactory();
14         Session sess = sf.openSession();
15         Transaction tx = sess.beginTransaction();
16         
17         Person p = new Person();
18         Address a = new Address("廣州天河");
19         //需要先持久化Address 對象
20         sess.persist(a);
21         p.setName("天王蓋地虎");
22         p.setAge(21);
23         p.getAddresses().add(a);
24         sess.save(p);
25         
26         Address a2 = new Address("上海虹口");
27         sess.persist(a2);
28         p.getAddresses().add(a2);
29     
30         tx.commit();
31         sess.close();
32         sf.close();
33     }
34 }

 

執行測試類,Hibernate同樣生成了person_inf和address_inf兩張表,

但是在address_inf表中,還加入了一個外鍵來關聯person_inf表,

表數據如下,可見對於person_inf表來說,person_id是主鍵,因此具有唯一約束,而它作爲address_inf表的外鍵,可以重複,

因此從person_inf表到address_inf表的映射關係來說,形成了1-N的單向關聯,

MariaDB [test]> select * from person_inf;
+-----------+------+------------+
| person_id | age  | name       |
+-----------+------+------------+
|         1 |   21 | 天王蓋地虎 |
+-----------+------+------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+-----------+
| address_id | addressDetail | person_id |
+------------+---------------+-----------+
|          1 | 廣州天河      |         1 |
|          2 | 上海虹口      |         1 |
+------------+---------------+-----------+
2 rows in set (0.00 sec)

 

表結構,

MariaDB [test]> desc address_inf;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| address_id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| addressDetail | varchar(255) | YES  |     | NULL    |                |
| person_id     | int(11)      | YES  | MUL | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

上面代碼的註解版本,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Column;
 8 import javax.persistence.Entity;
 9 import javax.persistence.GeneratedValue;
10 import javax.persistence.GenerationType;
11 import javax.persistence.Id;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.OneToMany;
14 import javax.persistence.Table;
15 
16 
17 @Entity
18 @Table(name="person_inf")
19 public class Person {
20     @Id @Column(name="person_id")
21     @GeneratedValue(strategy=GenerationType.IDENTITY)
22     private Integer id;
23     private int age;
24     private String name;
25     @OneToMany(targetEntity=Address.class)
26     @JoinColumn(name="person_id", referencedColumnName="person_id")
27     private Set<Address> addresses = new HashSet<>();
28     
29     public Integer getId() {
30         return id;
31     }
32     public void setId(Integer id) {
33         this.id = id;
34     }
35     public int getAge() {
36         return age;
37     }
38     public void setAge(int age) {
39         this.age = age;
40     }
41     public String getName() {
42         return name;
43     }
44     public void setName(String name) {
45         this.name = name;
46     }
47     public Set<Address> getAddresses() {
48         return addresses;
49     }
50     public void setAddresses(Set<Address> addresses) {
51         this.addresses = addresses;
52     }
53 }

 

另外,對於這個單向1-N關聯的例子,還有兩點需要注意,

1.cascade的用法,可以設置級聯更新

在測試類中,我們總是先持久化了Address實體,然後才持久化Person實體,這是爲了防止Hibernate報錯。

查看Hibernate的日誌,我們發現其SQL語句順序如下,

1 Hibernate: insert into address_inf (addressDetail) values (?)
2 Hibernate: insert into person_inf (age, name) values (?, ?)
3 Hibernate: insert into address_inf (addressDetail) values (?)
4 Hibernate: update address_inf set person_id=? where address_id=?
5 Hibernate: update address_inf set person_id=? where address_id=?

 

我們發現,Hibernate在插入記錄到address_inf表的時候,是要分爲兩步的,第一步是插入一條person_id爲null的記錄,第二步是將person表的person_id更新進address_inf表中。

是想如果我們不在測試類中顯式地先持久化Address類,當第二步要將person_id更新進address_inf表的時候,根本就找不到對應的記錄,那麼hibernate就會報錯了。

解決這個問題的另一個辦法就是設置級聯更新,即在person的映射文件中,爲address屬性添加cascade屬性,

1 <set name="addresses" table="address_inf" cascade="all">
2     <key column="person_id"/>
3     <one-to-many class="Address" />
4 </set>

如果是通過註解的方式實現的話,則是,

1 @OneToMany(targetEntity=Address.class, cascade=CascadeType.ALL)
2 @JoinColumn(name="person_id", referencedColumnName="person_id")
3 private Set<Address> addresses = new HashSet<>();

 

這樣就能保證即使沒有先顯示地持久化Address,只要person有更新,所關聯的address也會先持久化了。

添加了cascade屬性之後hibernate的日誌如下,

1 Hibernate: insert into person_inf (age, name) values (?, ?)
2 Hibernate: insert into address_inf (addressDetail) values (?)
3 Hibernate: insert into address_inf (addressDetail) values (?)
4 Hibernate: update address_inf set person_id=? where address_id=?
5 Hibernate: update address_inf set person_id=? where address_id=?

 

2.單向1-N的性能不高

從上面第1點也可以看出,對於address的持久化,無法通過一條sql語句實現,即在insert into 的時候指定好person_id值,而是需要一條insert和一條update才能實現,這樣性能就不高了。

對於這個問題,雙向的1-N關聯就可以得到解決了,將關聯控制轉到N的一方,就可以只通過一條SQL語句實現關聯實體的持久化。雙向關聯在後面再總結。

有連接表的單向1-N關聯

對於有連接表的單向1-N關聯,不再使用<one-to-many>標籤,而是使用<many-to-many unique="true">標籤,注意要使用unique="true"。不過如果使用JPA 註解的話,依然使用@OneToMany.

當然對於使用連接表實現的1-N關聯(集合關聯),在映射文件中不再使用<join>標籤,而是使用<set>標籤。

只不過在無連接表的例子中,<set>直接映射address_inf表,而在這個例子中,只需要將<set>映射單獨的關聯表person_address即可。

如果使用連接表,則只需要修改Person.hbm.xml

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <set name="addresses" table="person_address">
13             <key column="person_id"/>
14             <many-to-many class="Address" column="AddressID" unique="true" />
15         </set>
16     </class>
17 </hibernate-mapping>

 

執行程序,Hibernate生成了一張person_address表,

表數據,

MariaDB [test]> select * from person_inf;
+-----------+------+------------+
| person_id | age  | name       |
+-----------+------+------------+
|         1 |   21 | 天王蓋地虎 |
+-----------+------+------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+
| address_id | addressDetail |
+------------+---------------+
|          1 | 廣州天河      |
|          2 | 上海虹口      |
+------------+---------------+
2 rows in set (0.00 sec)

MariaDB [test]> select * from person_address;
+-----------+-----------+
| person_id | AddressID |
+-----------+-----------+
|         1 |         1 |
|         1 |         2 |
+-----------+-----------+
2 rows in set (0.00 sec)

 

表結構

MariaDB [test]> desc person_address;
+-----------+---------+------+-----+---------+-------+
| Field     | Type    | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| person_id | int(11) | NO   | PRI | NULL    |       |
| AddressID | int(11) | NO   | PRI | NULL    |       |
+-----------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)

字段addressID唯一約束

MariaDB [test]> SELECT TABLE_NAME,CONSTRAINT_NAME,CONSTRAINT_TYPE FROM informati
on_schema.`TABLE_CONSTRAINTS` WHERE TABLE_NAME = 'person_address' ;
+----------------+------------------------------+-----------------+
| TABLE_NAME     | CONSTRAINT_NAME              | CONSTRAINT_TYPE |
+----------------+------------------------------+-----------------+
| person_address | PRIMARY                      | PRIMARY KEY     |
| person_address | UK_i6yxiouhrtu6lpw50i8n7vbi1 | UNIQUE          |
| person_address | FK_anrg3ju8wu2kes1a2gr8bp7kg | FOREIGN KEY     |
| person_address | FK_i6yxiouhrtu6lpw50i8n7vbi1 | FOREIGN KEY     |
+----------------+------------------------------+-----------------+
4 rows in set (0.00 sec)

MariaDB [test]>

可見對於person_inf表來說,person_id是唯一的,hibernate 將person_id做爲了person_address的外鍵,同時將address_inf的主鍵address_id也做爲了person_address表的外鍵,但是對person_address表的addressID添加了唯一約束,這就實現了person表和address表在person_address表中1(addressID)-N(person_id)的關聯關係.

下面是JPA 註解版,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Column;
 8 import javax.persistence.Entity;
 9 import javax.persistence.GeneratedValue;
10 import javax.persistence.GenerationType;
11 import javax.persistence.Id;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.JoinTable;
14 import javax.persistence.OneToMany;
15 import javax.persistence.Table;
16 
17 
18 @Entity
19 @Table(name="person_inf")
20 public class Person {
21     @Id @Column(name="person_id")
22     @GeneratedValue(strategy=GenerationType.IDENTITY)
23     private Integer id;
24     private int age;
25     private String name;
26     @OneToMany(targetEntity = Address.class)
27     @JoinTable(name = "person_address", joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "person_id"),  
28     inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "address_id", unique = true))
29     private Set<Address> addresses = new HashSet<>();
30  
31     public Integer getId() {
32         return id;
33     }
34     public void setId(Integer id) {
35         this.id = id;
36     }
37     public int getAge() {
38         return age;
39     }
40     public void setAge(int age) {
41         this.age = age;
42     }
43     public String getName() {
44         return name;
45     }
46     public void setName(String name) {
47         this.name = name;
48     }
49     public Set<Address> getAddresses() {
50         return addresses;
51     }
52     public void setAddresses(Set<Address> addresses) {
53         this.addresses = addresses;
54     }
55 }

注意XML映射文件版與JPA註解版的區別,在映射文件中,是通過使用<many-to-many unique="true">標籤來映射1-N,而在JPA註解版中,可以直接使用@OneToMany註解了,從語義上來說,註解版更加直觀。

單項N-N關聯

單向的N-N關聯和1-N關聯的持久化類完全一樣,都是在控制關係的一端增加集合屬性(Set),被關聯的實體則以集合形式存在。N-N關聯必須使用連接表,而且與使用連接表的單向1-N關聯非常的相似,只是去掉了unique="true"的限制。映射文件如下,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <set name="addresses" table="person_address">
13             <key column="person_id"/>
14             <many-to-many class="Address" column="AddressID" />
15         </set>
16     </class>
17 </hibernate-mapping>

 執行結果與1-N關聯一模一樣,只不過生成的person_address表的addressID字段不再有唯一約束。

 無連接表的雙向1-N關聯

雙向1-N關聯不僅對於實體類需要增加管理屬性(集合),在關聯實體中也需要增加外鍵,因此實體類和關聯類都需要修改。同時,Hibernate建議不要在1的一端控制關係,因此要在1的一端的映射文件加inverse="true"屬性(JPA註解版則是加mappedBy)屬性。

Person實體類,注意加粗的關聯集合,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 
 7 public class Person {
 8     private Integer id;
 9     private int age;
10     private String name;
11     private Set<Address> addresses = new HashSet<>(); 12  
13     public Integer getId() {
14         return id;
15     }
16     public void setId(Integer id) {
17         this.id = id;
18     }
19     public int getAge() {
20         return age;
21     }
22     public void setAge(int age) {
23         this.age = age;
24     }
25     public String getName() {
26         return name;
27     }
28     public void setName(String name) {
29         this.name = name;
30     }
31     public Set<Address> getAddresses() {
32         return addresses;
33     }
34     public void setAddresses(Set<Address> addresses) {
35         this.addresses = addresses;
36     }
37 }

 

映射文件,指定關聯實體類,及關聯的外鍵person_id,同時設置inverse=true屬性表明1的一端不再控制關係。

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <set name="addresses" inverse="true">
13             <key column="person_id"/>
14             <one-to-many class="Address" />
15         </set>
16     </class>
17 </hibernate-mapping>

 

關聯的實體類Address, 注意加粗的實體類引用,

 1 package map.six11;
 2 
 3 public class Address {
 4     private Integer addressId;
 5     private String addressDetail;
 6     private Person person;  7     public Integer getAddressId() {
 8         return addressId;
 9     }
10     public void setAddressId(Integer addressId) {
11         this.addressId = addressId;
12     }
13     public String getAddressDetail() {
14         return addressDetail;
15     }
16     public void setAddressDetail(String addressDetail) {
17         this.addressDetail = addressDetail;
18     }
19     public Address() {}
20     public Address(String addressDetail) {
21         this.addressDetail = addressDetail;
22     }
23     public Person getPerson() {
24         return person;
25     }
26     public void setPerson(Person person) {
27         this.person = person;
28     }
29 }

 

Address的映射文件,通過many-to-one>關聯引用實體類,也需要指定外鍵,並且必須和Person指定的是同一個外鍵。

設置not-null可以保證每條從表記錄(address_inf)都有與之對應的主表(person_inf)記錄。

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail"  />
11         <!-- 雙向1-N關聯的關聯實體中,必須指定person_id且與實體類的Set屬性中的key column相同 -->
12         <many-to-one name="person" class="Person" column="person_id" not-null="true" />
13     </class>
14 </hibernate-mapping>

 

測試類如***意總是先持久化實體類,再設置關聯類與持久類關係,最後持久化關聯類,

 1 package map.six11;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 
 8 public class PersonManager {
 9     public static void main(String[] args) {
10         Configuration conf = new Configuration().configure();
11         conf.addClass(Person.class);
12         conf.addClass(Address.class);
13         //conf.addAnnotatedClass(Person.class);
14         //conf.addAnnotatedClass(Address.class);
15         SessionFactory sf = conf.buildSessionFactory();
16         Session sess = sf.openSession();
17         Transaction tx = sess.beginTransaction();
18         
19         //先持久化主表 
20         Person p = new Person();
21         p.setName("天王蓋地虎");
22         p.setAge(21);
23         sess.save(p);
24         
25         Address a = new Address("廣州天河");
26         //先設置關聯,再持久化a
27         a.setPerson(p);
28         sess.persist(a);
29         
30         Address a2 = new Address("上海虹口");
31         //先設置關聯,再持久化a
32         a2.setPerson(p);
33         sess.persist(a2);
34     
35         tx.commit();
36         sess.close();
37         sf.close();
38     }
39 }

 

執行測試類,發現Hibernate一共只執行了3條SQL語句完成持久化,

Hibernate: insert into person_inf (age, name) values (?, ?)
Hibernate: insert into adddress_inf (addressDetail, person_id) values (?, ?)
Hibernate: insert into adddress_inf (addressDetail, person_id) values (?, ?)

 

在mysql中,address_inf表增加了一個person_id外鍵字段,

MariaDB [test]> select * from person_inf;
+-----------+-----+------------+
| person_id | age | name       |
+-----------+-----+------------+
|         1 |  21 | 天王蓋地虎 |
+-----------+-----+------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+-----------+
| address_id | addressDetail | person_id |
+------------+---------------+-----------+
|          1 | 廣州天河      |         1 |
|          2 | 上海虹口      |         1 |
+------------+---------------+-----------+
2 rows in set (0.00 sec)

MariaDB [test]> desc address_inf;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| address_id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| addressDetail | varchar(255) | YES  |     | NULL    |                |
| person_id     | int(11)      | NO   | MUL | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

 

下面是JPA註解版,

Person爲addressess集合屬性添加@OneToMany屬性並設置mappedBy="person"表明不再控制關係,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Column;
 8 import javax.persistence.Entity;
 9 import javax.persistence.GeneratedValue;
10 import javax.persistence.GenerationType;
11 import javax.persistence.Id;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.JoinTable;
14 import javax.persistence.OneToMany;
15 import javax.persistence.Table;
16 
17 
18 @Entity
19 @Table(name="person_inf")
20 public class Person {
21     @Id @Column(name="person_id")
22     @GeneratedValue(strategy=GenerationType.IDENTITY)
23     private Integer id;
24     private int age;
25     private String name;
26     @OneToMany(targetEntity=Address.class, mappedBy="person")
27     private Set<Address> addresses = new HashSet<>();
28  
29     public Integer getId() {
30         return id;
31     }
32     public void setId(Integer id) {
33         this.id = id;
34     }
35     public int getAge() {
36         return age;
37     }
38     public void setAge(int age) {
39         this.age = age;
40     }
41     public String getName() {
42         return name;
43     }
44     public void setName(String name) {
45         this.name = name;
46     }
47     public Set<Address> getAddresses() {
48         return addresses;
49     }
50     public void setAddresses(Set<Address> addresses) {
51         this.addresses = addresses;
52     }
53 }

 

關聯實體類,爲關聯實體person設置@ManyToOne註解並設置person_id爲外鍵。

 1 package map.six11;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.GenerationType;
 7 import javax.persistence.Id;
 8 import javax.persistence.JoinColumn;
 9 import javax.persistence.ManyToOne;
10 import javax.persistence.Table;
11 
12 @Entity
13 @Table(name="address_inf")
14 public class Address {
15     @Id @Column(name="address_id")
16     @GeneratedValue(strategy=GenerationType.IDENTITY)
17     private Integer addressId;
18     private String addressDetail;
19     @ManyToOne(targetEntity=Person.class)
20     @JoinColumn(name="person_id", referencedColumnName="person_id", nullable=false)
21     private Person person;
22     public Integer getAddressId() {
23         return addressId;
24     }
25     public void setAddressId(Integer addressId) {
26         this.addressId = addressId;
27     }
28     public String getAddressDetail() {
29         return addressDetail;
30     }
31     public void setAddressDetail(String addressDetail) {
32         this.addressDetail = addressDetail;
33     }
34     public Address() {}
35     public Address(String addressDetail) {
36         this.addressDetail = addressDetail;
37     }
38     public Person getPerson() {
39         return person;
40     }
41     public void setPerson(Person person) {
42         this.person = person;
43     }
44 }

 

對於雙向1-N關聯,需要注意以下幾點

1.對於1-N關聯,Hibernate推薦使用雙向1-N關聯,並且不要讓1的一端控制關聯關係。(通過設置inverse=true或mappedBy實現)

2.出於性能考慮,應該先持久化1的一端實體類,這樣在insert  N的一端數據時候,就可以直接指定外鍵的值了,否則就需要update才能實現,多了一條SQL

3.先設置關聯關係(本例中的a.setPerson(person)), 再保存關聯對象,也是處於性能提升的考量。

 

雙向有連接表的1-N關聯

雙向1-N關聯也可以指定連接表,表中有兩個字段,分別是實體類與關聯類的主鍵即可。在映射文件中,1和N兩端都添加關聯到連接表。

在1的一端,與單向有連接表的1-N關聯非常類似,但又不是完全相同。 在單向1-N有連接表關聯時,我們可以用<set>標籤關聯連接表,用<key>關聯本類主鍵到連接表作爲外鍵,指定<many-to-many class="Address" column="AddressID" unique="true" />來關聯集合屬性,這裏的column名稱將會是關聯表裏的字段名稱,我們可以定義爲任意名字;然而在雙向1-N有連接表關聯中,這裏的column名字不能隨意指定,必須跟關聯實體類中,對應的字段名稱一致,關聯實體中的字段名字叫address_id,因而這裏(person映射文件及關聯表字段)名稱也必須是address_id。

而在N的一端,需要添加<join>標籤強制關聯連接表,也用<key>關聯到本類主鍵連接表作爲外鍵,指定<many-to-one name="person", column="person_id", not-null="true" />關聯實體類,這裏的column也不能隨意命名,必須是person表中對應的字段名稱。映射文件關鍵部分代碼如下,

person映射文件,

1 <set name="addresses" inverse="true" table="person_address">
2     <key column="person_id" />
3     <many-to-many class="Address" column="address_id" unique="true" />
4 </set>

 

Address映射文件,

1 <join table="person_address">
2     <key column="address_id" />
3     <many-to-one name="person" column="person_id" not-null="true" />
4 </join>

 

完整代碼如下,

person實體類,添加集合屬性 addresses,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 
 7 public class Person {
 8     private Integer id;
 9     private int age;
10     private String name;
11     private Set<Address> addresses = new HashSet<>();
12  
13     public Integer getId() {
14         return id;
15     }
16     public void setId(Integer id) {
17         this.id = id;
18     }
19     public int getAge() {
20         return age;
21     }
22     public void setAge(int age) {
23         this.age = age;
24     }
25     public String getName() {
26         return name;
27     }
28     public void setName(String name) {
29         this.name = name;
30     }
31     public Set<Address> getAddresses() {
32         return addresses;
33     }
34     public void setAddresses(Set<Address> addresses) {
35         this.addresses = addresses;
36     }
37 }

 

映射文件,指定連接表和關聯類及關聯字段

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <set name="addresses" inverse="true" table="person_address">
13             <key column="person_id" />
14             <many-to-many class="Address" column="address_id" unique="true" />
15         </set>
16     </class>
17 </hibernate-mapping>

 

Address實體類,添加Person實體類的引用,

 1 package map.six11;
 2 
 3 public class Address {
 4     private Integer addressId;
 5     private String addressDetail;
 6     private Person person;  7     public Integer getAddressId() {
 8         return addressId;
 9     }
10     public void setAddressId(Integer addressId) {
11         this.addressId = addressId;
12     }
13     public String getAddressDetail() {
14         return addressDetail;
15     }
16     public void setAddressDetail(String addressDetail) {
17         this.addressDetail = addressDetail;
18     }
19     public Address() {}
20     public Address(String addressDetail) {
21         this.addressDetail = addressDetail;
22     }
23     public Person getPerson() {
24         return person;
25     }
26     public void setPerson(Person person) {
27         this.person = person;
28     }
29 }

 

映射文件,指定連接表和關聯類及關聯字段,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail"  />
11         <join table="person_address">
12             <key column="address_id" />
13             <many-to-one name="person" column="person_id" not-null="true" />
14         </join>
15     </class>
16 </hibernate-mapping>

 

測試類,

 1 package map.six11;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 
 8 public class PersonManager {
 9     public static void main(String[] args) {
10         Configuration conf = new Configuration().configure();
11         conf.addClass(Person.class);
12         conf.addClass(Address.class);
13         //conf.addAnnotatedClass(Person.class);
14         //conf.addAnnotatedClass(Address.class);
15         SessionFactory sf = conf.buildSessionFactory();
16         Session sess = sf.openSession();
17         Transaction tx = sess.beginTransaction();
18         
19         //先持久化主表 
20         Person p = new Person();
21         p.setName("天王蓋地虎");
22         p.setAge(21);
23         sess.save(p);
24         
25         Address a = new Address("廣州天河");
26         //先設置關聯,再持久化a
27         a.setPerson(p);
28         sess.persist(a);
29         
30         Address a2 = new Address("上海虹口");
31         //先設置關聯,再持久化a
32         a2.setPerson(p);
33         sess.persist(a2);
34     
35         tx.commit();
36         sess.close();
37         sf.close();
38     }
39 }

 

執行測試類,Hibernate生成3張表,在person_address表上有兩個外鍵約束,分別關聯person_inf和address_inf的主鍵,Hibernate爲上面每一個持久化生成一個insert語句,加上爲連接表插入數據的語句,一共有5條SQL語句,

Hibernate: insert into person_inf (age, name) values (?, ?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: insert into person_address (person_id, address_id) values (?, ?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: insert into person_address (person_id, address_id) values (?, ?)

 

表數據和結構如下,

MariaDB [information_schema]> SELECT * FROM `TABLE_CONSTRAINTS` WHERE `TABLE_NAME` = 'person_address' ;
+--------------------+-------------------+------------------------------+--------------+----------------+-----------------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME              | TABLE_SCHEMA | TABLE_NAME     | CONSTRAINT_TYPE |
+--------------------+-------------------+------------------------------+--------------+----------------+-----------------+
| def                | test              | PRIMARY                      | test         | person_address | PRIMARY KEY     |
| def                | test              | FK_anrg3ju8wu2kes1a2gr8bp7kg | test         | person_address | FOREIGN KEY     |
| def                | test              | FK_d0akgh2385j4j0w78l54f6lkg | test         | person_address | FOREIGN KEY     |
+--------------------+-------------------+------------------------------+--------------+----------------+-----------------+
3 rows in set (0.00 sec)

MariaDB [information_schema]> use test;
Database changed
MariaDB [test]> select * from person_address;
+------------+-----------+
| address_id | person_id |
+------------+-----------+
|          1 |         1 |
|          2 |         1 |
+------------+-----------+
2 rows in set (0.00 sec)

下面是JPA註解版

相比起來,註解版就簡單多了,Person實體類與不帶連接表的雙向1-N關聯中的實體類一模一樣不需要改變,僅僅是在Address實體類中加入@ManyToOne和@JoinTable,

Address實體類,

 1 package map.six11;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.GenerationType;
 7 import javax.persistence.Id;
 8 import javax.persistence.JoinTable;
 9 import javax.persistence.JoinColumn;
10 import javax.persistence.ManyToOne;
11 import javax.persistence.Table;
12 
13 @Entity
14 @Table(name="address_inf")
15 public class Address {
16     @Id @Column(name="address_id")
17     @GeneratedValue(strategy=GenerationType.IDENTITY)
18     private Integer addressId;
19     private String addressDetail;
20     @ManyToOne(targetEntity=Person.class)
21     @JoinTable(name="person_address",
22             [email protected](name="address_id", referencedColumnName="address_id", unique=true),
23             [email protected](name="person_id", referencedColumnName="person_id")
24     )
25     private Person person;
26     public Integer getAddressId() {
27         return addressId;
28     }
29     public void setAddressId(Integer addressId) {
30         this.addressId = addressId;
31     }
32     public String getAddressDetail() {
33         return addressDetail;
34     }
35     public void setAddressDetail(String addressDetail) {
36         this.addressDetail = addressDetail;
37     }
38     public Address() {}
39     public Address(String addressDetail) {
40         this.addressDetail = addressDetail;
41     }
42     public Person getPerson() {
43         return person;
44     }
45     public void setPerson(Person person) {
46         this.person = person;
47     }
48 }

 

 執行結果與前面一樣。

雙向N-N關聯

雙向N-N關聯只能用連接表實現。在關聯實體兩邊都添加Set集合屬性,在映射文件中都使用<Set>集合標籤關聯連接表,都使用<key>標籤標識連接表外鍵(實體類主鍵),都使用<many-to-many>表示被關聯實體類並指明關聯的字段,都不能使用unique=true屬性因爲是多對多關聯。

Person實體類映射文件,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <set name="addresses" inverse="true" table="person_address">
13             <key column="person_id" />
14             <many-to-many class="Address" column="address_id" />
15         </set>
16     </class>
17 </hibernate-mapping>

 

 Address實體類映射文件,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail"  />
11         <join table="person_address">
12             <key column="address_id" />
13             <many-to-one name="person" column="person_id" not-null="true" />
14         </join>
15     </class>
16 </hibernate-mapping>

 

下面是JPA註解版,

Person實體,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.Column;
 7 import javax.persistence.Entity;
 8 import javax.persistence.GeneratedValue;
 9 import javax.persistence.GenerationType;
10 import javax.persistence.Id;
11 import javax.persistence.JoinTable;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.ManyToMany;
14 import javax.persistence.OneToMany;
15 import javax.persistence.Table;
16 
17 @Entity
18 @Table(name="person_inf")
19 public class Person {
20     @Id @Column(name="person_id")
21     @GeneratedValue(strategy=GenerationType.IDENTITY)
22     private Integer id;
23     private int age;
24     private String name;
25     @ManyToMany(targetEntity=Address.class)
26     @JoinTable(name="person_address",
27     [email protected](name="person_id", referencedColumnName="person_id"),
28     [email protected](name="address_id", referencedColumnName="address_id"))
29     private Set<Address> addresses = new HashSet<>();
30  
31     public Integer getId() {
32         return id;
33     }
34     public void setId(Integer id) {
35         this.id = id;
36     }
37     public int getAge() {
38         return age;
39     }
40     public void setAge(int age) {
41         this.age = age;
42     }
43     public String getName() {
44         return name;
45     }
46     public void setName(String name) {
47         this.name = name;
48     }
49     public Set<Address> getAddresses() {
50         return addresses;
51     }
52     public void setAddresses(Set<Address> addresses) {
53         this.addresses = addresses;
54     }
55 }

 

Address實體,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.Column;
 7 import javax.persistence.Entity;
 8 import javax.persistence.GeneratedValue;
 9 import javax.persistence.GenerationType;
10 import javax.persistence.Id;
11 import javax.persistence.JoinTable;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.ManyToMany;
14 import javax.persistence.ManyToOne;
15 import javax.persistence.Table;
16 
17 @Entity
18 @Table(name="address_inf")
19 public class Address {
20     @Id @Column(name="address_id")
21     @GeneratedValue(strategy=GenerationType.IDENTITY)
22     private Integer addressId;
23     private String addressDetail;
24     @ManyToMany(targetEntity=Person.class)
25     @JoinTable(name="person_address",
26             [email protected](name="address_id", referencedColumnName="address_id"),
27             [email protected](name="person_id", referencedColumnName="person_id")
28     )
29     private Set<Person> person = new HashSet<>();
30     public Set<Person> getPerson() {
31         return person;
32     }
33     public void setPerson(Set<Person> person) {
34         this.person = person;
35     }
36     public Integer getAddressId() {
37         return addressId;
38     }
39     public void setAddressId(Integer addressId) {
40         this.addressId = addressId;
41     }
42     public String getAddressDetail() {
43         return addressDetail;
44     }
45     public void setAddressDetail(String addressDetail) {
46         this.addressDetail = addressDetail;
47     }
48     public Address() {}
49     public Address(String addressDetail) {
50         this.addressDetail = addressDetail;
51     }
52 }

 

 

雙向1-1關聯 (基於外鍵)

通過在表中添加外鍵列實現1-1關聯,外鍵可以添加在任意一邊表中,映射文件則添加<many-to-one unique="true">標籤來關聯另一個實體,未添加外鍵的實體映射文件則用<one-to-one>來關聯實體,關鍵代碼如下。

現在假如將外鍵列添加在address_inf表中,

則Person映射文件爲,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <one-to-one name="address" property-ref="person"/>
13     </class>
14 </hibernate-mapping>

 

Address映射文件爲,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail"  />
11         <many-to-one name="person" unique="true" column="person_id" not-null="true" />
12     </class>
13 </hibernate-mapping>

 

如果用JPA註解實現,則Person實體類爲,

 1 package map.six11;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.GenerationType;
 7 import javax.persistence.Id;
 8 import javax.persistence.OneToOne;
 9 import javax.persistence.Table;
10 
11 
12 @Entity
13 @Table(name="person_inf")
14 public class Person {
15     @Id @Column(name="person_id")
16     @GeneratedValue(strategy=GenerationType.IDENTITY)
17     private Integer id;
18     private int age;
19     private String name;
20     @OneToOne(targetEntity=Address.class, mappedBy="person")
21     private Address address;
22  
23     public Integer getId() {
24         return id;
25     }
26     public void setId(Integer id) {
27         this.id = id;
28     }
29     public int getAge() {
30         return age;
31     }
32     public void setAge(int age) {
33         this.age = age;
34     }
35     public String getName() {
36         return name;
37     }
38     public void setName(String name) {
39         this.name = name;
40     }
41     public Address getAddress() {
42         return address;
43     }
44     public void setAddress(Address address) {
45         this.address = address;
46     }
47 }

 

Address實體類如下,兩邊實體類都使用的是@OneToOne, 這更符合語意。 在Address這邊,顯示地指明瞭關聯外鍵列。

 1 package map.six11;
 2 
 3 
 4 import javax.persistence.Column;
 5 import javax.persistence.Entity;
 6 import javax.persistence.GeneratedValue;
 7 import javax.persistence.GenerationType;
 8 import javax.persistence.Id;
 9 import javax.persistence.JoinColumn;
10 import javax.persistence.ManyToOne;
11 import javax.persistence.OneToOne;
12 import javax.persistence.Table;
13 
14 
15 @Entity
16 @Table(name="address_inf")
17 public class Address {
18     @Id @Column(name="address_id")
19     @GeneratedValue(strategy=GenerationType.IDENTITY)
20     private Integer addressId;
21     private String addressDetail;
22     @OneToOne(targetEntity=Person.class)
23     @JoinColumn(name="person_id", referencedColumnName="person_id", unique=true)
24     private Person person;
25 
26     public Integer getAddressId() {
27         return addressId;
28     }
29     public void setAddressId(Integer addressId) {
30         this.addressId = addressId;
31     }
32     public String getAddressDetail() {
33         return addressDetail;
34     }
35     public void setAddressDetail(String addressDetail) {
36         this.addressDetail = addressDetail;
37     }
38     public Address() {}
39     public Address(String addressDetail) {
40         this.addressDetail = addressDetail;
41     }
42     public Person getPerson() {
43         return person;
44     }
45     public void setPerson(Person person) {
46         this.person = person;
47     }
48 }

雙向1-1關聯 (基於主鍵)

基於主鍵的雙向1-1關聯,兩邊都使用<one-to-one>關聯另對方實體類,只不過需要在其中一個實體類中,將主鍵生成器設置爲foreign,使其變成從表,

比如在Address映射文件中,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="foreign" >
 9                 <param name="property">person</param>
10             </generator>
11         </id>
12         <property name="addressDetail"  />
13         <one-to-one name="person" />
14     </class>
15 </hibernate-mapping>

 

在Person映射文件中,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <one-to-one name="address" />
13     </class>
14 </hibernate-mapping>

 雙向1-1關聯 (基於連接表)

Hibernate並不推薦基於連接表的雙向1-1關聯,因爲映射比較複雜。

在實體兩邊,都使用<join>標籤指定連接表,都使用<many-to-one>映射關聯類。

Person映射文件如下,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <join table="person_address" inverse="true">
13             <key column="person_id" unique="true" />
14             <many-to-one name="address" class="Address" column="address_id" unique="true" />
15         </join>
16     </class>
17 </hibernate-mapping>

 

Address映射文件如下,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Address" table="address_inf">
 7         <id name="addressId" column="address_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="addressDetail" />
11         <join table="person_address" optional="true">
12             <key column="person_id" unique="true" />
13             <many-to-one name="person" class="Person" column="person_id"
14                 unique="true" />
15         </join>
16     </class>
17 </hibernate-mapping>

 

兩個映射文件的區別是,Address的address_id將作爲連接表的主鍵,因此Address映射文件爲<join 指定optional=true屬性,而Person則爲<join指定inverse=true屬性。

下面是JPA註解實現,兩邊都是用@OneToOne註解映射關聯類,兩邊都使用@JoinTable表示連接表,兩個實體類寫法都是一樣的,只是互相引用對方而已,

 Person實體類,

 1 package map.six11;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.GenerationType;
 7 import javax.persistence.Id;
 8 import javax.persistence.JoinTable;
 9 import javax.persistence.JoinColumn;
10 import javax.persistence.OneToOne;
11 import javax.persistence.Table;
12 
13 
14 @Entity
15 @Table(name="person_inf")
16 public class Person {
17     @Id @Column(name="person_id")
18     @GeneratedValue(strategy=GenerationType.IDENTITY)
19     private Integer id;
20     private int age;
21     private String name;
22     @OneToOne(targetEntity=Address.class)
23     @JoinTable(name="person_address",
24     [email protected](name="person_id", referencedColumnName="person_id", unique=true),
25     [email protected](name="access_id", referencedColumnName="address_id", unique=true)
26     )
27     private Address address;
28  
29     public Integer getId() {
30         return id;
31     }
32     public void setId(Integer id) {
33         this.id = id;
34     }
35     public int getAge() {
36         return age;
37     }
38     public void setAge(int age) {
39         this.age = age;
40     }
41     public String getName() {
42         return name;
43     }
44     public void setName(String name) {
45         this.name = name;
46     }
47     public Address getAddress() {
48         return address;
49     }
50     public void setAddress(Address address) {
51         this.address = address;
52     }
53 }

 

Address實體類,

 1 package map.six11;
 2 
 3 
 4 import javax.persistence.Column;
 5 import javax.persistence.Entity;
 6 import javax.persistence.GeneratedValue;
 7 import javax.persistence.GenerationType;
 8 import javax.persistence.Id;
 9 import javax.persistence.JoinColumn;
10 import javax.persistence.JoinTable;
11 import javax.persistence.ManyToOne;
12 import javax.persistence.OneToOne;
13 import javax.persistence.Table;
14 
15 
16 @Entity
17 @Table(name="address_inf")
18 public class Address {
19     @Id @Column(name="address_id")
20     @GeneratedValue(strategy=GenerationType.IDENTITY)
21     private Integer addressId;
22     private String addressDetail;
23     @OneToOne(targetEntity=Person.class)
24     @JoinTable(name="person_address",
25     [email protected](name="address_id", referencedColumnName="address_id", unique=true),
26     [email protected](name="person_id", referencedColumnName="person_id", unique=true)
27     )
28     private Person person;
29 
30     public Integer getAddressId() {
31         return addressId;
32     }
33     public void setAddressId(Integer addressId) {
34         this.addressId = addressId;
35     }
36     public String getAddressDetail() {
37         return addressDetail;
38     }
39     public void setAddressDetail(String addressDetail) {
40         this.addressDetail = addressDetail;
41     }
42     public Address() {}
43     public Address(String addressDetail) {
44         this.addressDetail = addressDetail;
45     }
46     public Person getPerson() {
47         return person;
48     }
49     public void setPerson(Person person) {
50         this.person = person;
51     }
52 }

 組件屬性包含關聯實體

如果一個持久化類Person的一個組件屬性Address中,又有一個屬性School也是一個持久化類,那麼從邏輯上來講,Address和School應該是關聯的,但是如果Address僅僅是一個組件而不是實體類,那麼就會變成Person和School關聯。

比如下面這樣,Person類中有一個組件屬性Address,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 
 7 public class Person {
 8     private Integer id;
 9     private int age;
10     private String name;
11     private Address address;
12  
13     public Integer getId() {
14         return id;
15     }
16     public void setId(Integer id) {
17         this.id = id;
18     }
19     public int getAge() {
20         return age;
21     }
22     public void setAge(int age) {
23         this.age = age;
24     }
25     public String getName() {
26         return name;
27     }
28     public void setName(String name) {
29         this.name = name;
30     }
31     public Address getAddress() {
32         return address;
33     }
34     public void setAddress(Address address) {
35         this.address = address;
36     }
37 }

 

組件Address(注意這裏只當作組件用,不再當作持久化了類, 因此也不會有映射文件)

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 public class Address {
 7     private Integer addressId;
 8     private String addressDetail;
 9     private Person person;
10     private Set<School> schools = new HashSet<>();
11     public Integer getAddressId() {
12         return addressId;
13     }
14     public void setAddressId(Integer addressId) {
15         this.addressId = addressId;
16     }
17     public String getAddressDetail() {
18         return addressDetail;
19     }
20     public void setAddressDetail(String addressDetail) {
21         this.addressDetail = addressDetail;
22     }
23     public Address() {}
24     public Address(String addressDetail) {
25         this.addressDetail = addressDetail;
26     }
27     public Person getPerson() {
28         return person;
29     }
30     public void setPerson(Person person) {
31         this.person = person;
32     }
33     public Set<School> getSchools() {
34         return schools;
35     }
36     public void setSchools(Set<School> schools) {
37         this.schools = schools;
38     }
39 }

 

在組件屬性Address中包含了一個集合屬性school, shool又是一個持久化類,代碼如下,

 1 package map.six11;
 2 
 3 public class School {
 4     private Integer schoolId;
 5     private String schoolName;
 6 
 7     public String getSchoolName() {
 8         return schoolName;
 9     }
10 
11     public void setSchoolName(String schoolName) {
12         this.schoolName = schoolName;
13     }
14     
15     public School(){}
16     
17     public School(String schoolName) {
18         this.schoolName = schoolName;
19     }
20 
21     public Integer getSchoolId() {
22         return schoolId;
23     }
24 
25     public void setSchoolId(Integer schoolId) {
26         this.schoolId = schoolId;
27     }
28 }

 

可見,從邏輯上來說,Address與School形成1-N的單向關聯。

Person映射文件如下,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="Person" table="person_inf">
 7         <id name="id" column="person_id" type="int">
 8             <generator class="identity" />
 9         </id>
10         <property name="age" type="int" />
11         <property name="name" type="string" />
12         <!-- 映射主鍵元素 -->
13         <component name="address" class="Address">
14             <!-- 映射組件的包含實體 -->
15             <parent name="person" />
16             <property name="addressDetail" />
17             <!-- 映射組件類的集合屬性, 集合元素又是其他持久化實體類 -->
18             <set name="schools">
19                 <!-- 外鍵 -->
20                 <key column="address_id" />
21                 <!-- 1-N關聯 -->
22                 <one-to-many class="School" />
23             </set>
24         </component>
25     </class>
26 </hibernate-mapping>

 

Address不是持久化類,沒有映射文件。 School映射文件如下,

 1 <?xml version="1.0"  encoding="UTF-8"?>    
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 
 5 <hibernate-mapping package="map.six11">
 6     <class name="School" table="school_inf">
 7         <id name="schoolId" column="school_id">
 8             <generator class="identity" />
 9         </id>
10         <property name="schoolName" />
11     </class>
12 </hibernate-mapping>

 

測試類如下,

 1 package map.six11;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 
 8 public class PersonManager {
 9     public static void main(String[] args) {
10         Configuration conf = new Configuration().configure();
11         conf.addClass(Person.class);
12         conf.addClass(School.class);
13         //conf.addClass(Address.class);
14         //conf.addAnnotatedClass(Person.class);
15         //conf.addAnnotatedClass(Address.class);
16         SessionFactory sf = conf.buildSessionFactory();
17         Session sess = sf.openSession();
18         Transaction tx = sess.beginTransaction();
19         
20         //先持久化主表 
21         Person p = new Person();
22         p.setName("天王蓋地虎");
23         p.setAge(21);
24         sess.save(p);
25         
26         Address a = new Address("廣州天河");
27         p.setAddress(a);
28         
29         School s1 = new School("北京大學");
30         School s2 = new School("清華大學");
31         
32         sess.save(s1);
33         sess.save(s2);
34         
35         a.getSchools().add(s1);
36         a.getSchools().add(s2);
37     
38         tx.commit();
39         sess.close();
40         sf.close();
41     }
42 }

 

測試類中,我們只需要加載Person和School兩個類作爲持久化類。

程序必須先持久化兩個School對象,因爲School對象沒有對Address的引用,而映射文件要求有一個address_id外鍵,因此這必然導致insert之後的update。

執行測試類,Hibernate生成的SQL如下,

Hibernate: insert into person_inf (age, name, addressDetail) values (?, ?, ?)
Hibernate: insert into school_inf (schoolName) values (?)
Hibernate: insert into school_inf (schoolName) values (?)
Hibernate: update person_inf set age=?, name=?, addressDetail=? where person_id=?
Hibernate: update school_inf set address_id=? where school_id=?
Hibernate: update school_inf set address_id=? where school_id=?

 

查看數據庫的數據,發現生成兩張表,person_inf和school_inf, person_inf中有address信息,person_inf中則有address_inf外鍵。

從邏輯上來講,address得是一個持久化類,並和school形成1-N單向關聯,然而這裏的address根本不是一個持久化類,這導致了school_inf表與person_inf表關聯到了一起,看起來很混亂。

事實上,Hibernate也不推薦 組件裏包含關聯實體 , 因爲這確實沒啥意義,不如直接將組件也持久化。

表信息,

MariaDB [test]> select * from person_inf;
+-----------+------+------------+---------------+
| person_id | age  | name       | addressDetail |
+-----------+------+------------+---------------+
|         1 |   21 | 天王蓋地虎 | 廣州天河      |
+-----------+------+------------+---------------+
1 row in set (0.00 sec)

MariaDB [test]> select * from school_inf;
+-----------+------------+------------+
| school_id | schoolName | address_id |
+-----------+------------+------------+
|         1 | 北京大學   |          1 |
|         2 | 清華大學   |          1 |
+-----------+------------+------------+
2 rows in set (0.00 sec)

 

表約束,

下面附上JPA註解版,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Column;
 8 import javax.persistence.Entity;
 9 import javax.persistence.GeneratedValue;
10 import javax.persistence.GenerationType;
11 import javax.persistence.Id;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.JoinTable;
14 import javax.persistence.OneToMany;
15 import javax.persistence.Table;
16 
17 
18 @Entity
19 @Table(name="person_inf")
20 public class Person {
21     @Id @Column(name="person_id")
22     @GeneratedValue(strategy=GenerationType.IDENTITY)
23     private Integer id;
24     private int age;
25     private String name;
26     private Address address;
27  
28     public Integer getId() {
29         return id;
30     }
31     public void setId(Integer id) {
32         this.id = id;
33     }
34     public int getAge() {
35         return age;
36     }
37     public void setAge(int age) {
38         this.age = age;
39     }
40     public String getName() {
41         return name;
42     }
43     public void setName(String name) {
44         this.name = name;
45     }
46     public Address getAddress() {
47         return address;
48     }
49     public void setAddress(Address address) {
50         this.address = address;
51     }
52 }

 

 

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.Embeddable;
 7 import javax.persistence.JoinColumn;
 8 import javax.persistence.OneToMany;
 9 
10 
11 import org.hibernate.annotations.Parent;
12 
13 @Embeddable
14 public class Address {
15     private Integer addressId;
16     private String addressDetail;
17     @Parent
18     private Person person;
19     @OneToMany(targetEntity=School.class)
20     @JoinColumn(name="address_id", referencedColumnName="person_id")
21     private Set<School> schools = new HashSet<>();
22     public Integer getAddressId() {
23         return addressId;
24     }
25     public void setAddressId(Integer addressId) {
26         this.addressId = addressId;
27     }
28     public String getAddressDetail() {
29         return addressDetail;
30     }
31     public void setAddressDetail(String addressDetail) {
32         this.addressDetail = addressDetail;
33     }
34     public Address() {}
35     public Address(String addressDetail) {
36         this.addressDetail = addressDetail;
37     }
38     public Person getPerson() {
39         return person;
40     }
41     public void setPerson(Person person) {
42         this.person = person;
43     }
44     public Set<School> getSchools() {
45         return schools;
46     }
47     public void setSchools(Set<School> schools) {
48         this.schools = schools;
49     }
50 }

 

 

 1 package map.six11;
 2 
 3 import javax.persistence.Column;
 4 import javax.persistence.Entity;
 5 import javax.persistence.GeneratedValue;
 6 import javax.persistence.GenerationType;
 7 import javax.persistence.Id;
 8 import javax.persistence.Table;
 9 
10 @Entity
11 @Table(name="school_inf")
12 public class School {
13     @Id @Column(name="person_id")
14     @GeneratedValue(strategy=GenerationType.IDENTITY)
15     private Integer schoolId;
16     private String schoolName;
17 
18     public String getSchoolName() {
19         return schoolName;
20     }
21 
22     public void setSchoolName(String schoolName) {
23         this.schoolName = schoolName;
24     }
25     
26     public School(){}
27     
28     public School(String schoolName) {
29         this.schoolName = schoolName;
30     }
31 
32     public Integer getSchoolId() {
33         return schoolId;
34     }
35 
36     public void setSchoolId(Integer schoolId) {
37         this.schoolId = schoolId;
38     }
39 }

基於複合主鍵的關聯關係

雖然Hibernate不推薦複合主鍵,但依然提供了支持。下面的例子中,Person將會使用複合主鍵,在Person中有個Address集合屬性,其元素也是持久化實體,同時還在Address端添加了Person的引用,因此Person與Address之間形成雙向1-N關聯關係。

Person類,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.CascadeType;
 7 import javax.persistence.Entity;
 8 import javax.persistence.Id;
 9 import javax.persistence.OneToMany;
10 import javax.persistence.Table;
11 
12 
13 @Entity
14 @Table(name="person_inf")
15 public class Person implements java.io.Serializable{
16     @Id
17     private String first;
18     @Id
19     private String last;
20     private int age;
21     @OneToMany(targetEntity=Address.class, mappedBy="person", cascade=CascadeType.ALL)
22     private Set<Address> addresses = new HashSet<>();
23  
24     public int getAge() {
25         return age;
26     }
27     public Set<Address> getAddresses() {
28         return addresses;
29     }
30     public void setAddresses(Set<Address> addresses) {
31         this.addresses = addresses;
32     }
33     public void setAge(int age) {
34         this.age = age;
35     }
36     public String getFirst() {
37         return first;
38     }
39     public void setFirst(String first) {
40         this.first = first;
41     }
42     public String getLast() {
43         return last;
44     }
45     public void setLast(String last) {
46         this.last = last;
47     }
48     public boolean equals(Object obj) {
49         if (this==obj) return true;
50         if (obj != null && obj.getClass() == Person.class) {
51             Person target = (Person)obj;
52             return target.getFirst().equals(this.first) && target.getLast().equals(this.last);
53         }
54         return false;
55     }
56     public int hashCode() {
57         return getFirst().hashCode() * 31 + getLast().hashCode();
58     }
59 }

 

Address實體,

 1 package map.six11;
 2 
 3 import java.util.HashSet;
 4 import java.util.Set;
 5 
 6 import javax.persistence.Column;
 7 import javax.persistence.Embeddable;
 8 import javax.persistence.Entity;
 9 import javax.persistence.GeneratedValue;
10 import javax.persistence.GenerationType;
11 import javax.persistence.Id;
12 import javax.persistence.JoinColumn;
13 import javax.persistence.JoinColumns;
14 import javax.persistence.ManyToOne;
15 import javax.persistence.OneToMany;
16 
17 
18 
19 
20 import javax.persistence.Table;
21 
22 import org.hibernate.annotations.Parent;
23 
24 @Entity
25 @Table(name="address_inf")
26 public class Address implements java.io.Serializable {
27     @Id @Column(name="address_id")
28     @GeneratedValue(strategy=GenerationType.IDENTITY)
29     private Integer addressId;
30     private String addressDetail;
31     
32     @ManyToOne(targetEntity=Person.class)
33     @JoinColumns({@JoinColumn(name="person_first", referencedColumnName="first", nullable=false),
34         @JoinColumn(name="person_last", referencedColumnName="last", nullable=false)})
35     private Person person;
36     public Integer getAddressId() {
37         return addressId;
38     }
39     public void setAddressId(Integer addressId) {
40         this.addressId = addressId;
41     }
42     public String getAddressDetail() {
43         return addressDetail;
44     }
45     public void setAddressDetail(String addressDetail) {
46         this.addressDetail = addressDetail;
47     }
48     public Address() {}
49     public Address(String addressDetail) {
50         this.addressDetail = addressDetail;
51     }
52     public Person getPerson() {
53         return person;
54     }
55     public void setPerson(Person person) {
56         this.person = person;
57     }
58 }

 

測試類,

 1 package map.six11;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.Transaction;
 6 import org.hibernate.cfg.Configuration;
 7 
 8 public class PersonManager {
 9     public static void main(String[] args) {
10         Configuration conf = new Configuration().configure();
11         //conf.addClass(Person.class);
12         //conf.addClass(School.class);
13         //conf.addClass(Address.class);
14         conf.addAnnotatedClass(Person.class);
15         //conf.addAnnotatedClass(School.class);
16         conf.addAnnotatedClass(Address.class);
17         SessionFactory sf = conf.buildSessionFactory();
18         Session sess = sf.openSession();
19         Transaction tx = sess.beginTransaction();
20 
21         Person p = new Person();
22         p.setFirst("天王蓋地虎");
23         p.setLast("寶塔鎮河妖");
24         p.setAge(21);
25     
26         Address a = new Address("廣州天河");
27         a.setPerson(p);
28         Address a2 = new Address("上海虹橋");
29         a2.setPerson(p);
30  
31         p.getAddresses().add(a);
32         p.getAddresses().add(a2);
33         sess.save(p);
34         
35         tx.commit();
36         sess.close();
37         sf.close();
38     }
39 }

 

因爲在Person中設置了級聯更新(cascade),因此在測試類中不需要顯示地持久化Address。

Address中的引用屬性person添加了nullable=false屬性因此在需要在測試類中爲preson屬性賦值(a.setPerson(...))

執行測試類可以看到在address_inf表中,是通過兩個外鍵(person_first, person_last)與person_inf表關聯的,因爲person_inf表是複合主鍵。

 

MariaDB [test]> select * from person_inf;
+------------+------------+-----+
| last       | first      | age |
+------------+------------+-----+
| 寶塔鎮河妖 | 天王蓋地虎 |  21 |
+------------+------------+-----+
1 row in set (0.00 sec)

MariaDB [test]> select * from address_inf;
+------------+---------------+-------------+--------------+
| address_id | addressDetail | person_last | person_first |
+------------+---------------+-------------+--------------+
|          1 | 上海虹橋      | 寶塔鎮河妖  | 天王蓋地虎   |
|          2 | 廣州天河      | 寶塔鎮河妖  | 天王蓋地虎   |
+------------+---------------+-------------+--------------+
2 rows in set (0.00 sec)

 

表結構, 

複合主鍵的成員屬性爲關聯實體

這比前面的基於複合主鍵的關聯關係更復雜了一層,但實際項目中會有很多這樣的例子,例如下面這個經銷存系統。

該系統涉及訂單,商品,訂單項三個實體,其中一個訂單可以包含多個訂單項,一個訂單項用於訂購某種商品,以及訂購數量,一個商品可以包含在不同訂單項中。

從上面介紹來看,訂單和訂單項存在雙向1-N關係,訂單項和商品之間存在單向N-1關係。

這裏的訂單項是一個關鍵實體,同時直接關聯了訂單和商品,在實際項目中很多程序員不爲訂單項額外地設計一個邏輯主鍵,而是用 訂單主鍵+商品主鍵+訂購數量 作爲複合主鍵,Hibernate並不推薦這麼做,因爲這樣增加了程序複雜性。 對於這樣的設計,在Hibernate中就需要做一些特殊的映射了。

下面是訂單類,

 1 package map.six11;
 2 
 3 import java.util.Date;
 4 import java.util.HashSet;
 5 import java.util.Set;
 6 
 7 import javax.persistence.CascadeType;
 8 import javax.persistence.Column;
 9 import javax.persistence.Entity;
10 import javax.persistence.GeneratedValue;
11 import javax.persistence.GenerationType;
12 import javax.persistence.Id;
13 import javax.persistence.OneToMany;
14 import javax.persistence.Table;
15 
16 @Entity
17 @Table(name="Order_inf")
18 public class Order {
19     @Id @Column(name="order_id")
20     @GeneratedValue(strategy=GenerationType.IDENTITY)
21     private Integer orderId;
22     private Date orderDate;
23     @OneToMany(targetEntity=OrderItem.class, mappedBy="order")
24     private Set<OrderItem> items = new HashSet<>();
25     public Date getOrderDate() {
26         return orderDate;
27     }
28     public void setOrderDate(Date orderDate) {
29         this.orderDate = orderDate;
30     }
31     public Set<OrderItem> getItems() {
32         return items;
33     }
34     public void setItems(Set<OrderItem> items) {
35         this.items = items;
36     }
37     public Order() {}
38     public Order(Date orderDate) {
39         this.orderDate = orderDate;
40     }
41 }

 訂單類與訂單項存在1-N雙向關聯,所以在訂單類中有一個Set存放訂單項,用@OneToMany與之關聯並通過mappedBy設置訂單類不控制關係。

與之對應的是在訂單項類中,有一個訂單類的引用,訂單項與訂單存在N-1關聯,用@ManyToOne修飾,指定訂單id爲關聯外鍵,

同時還需要有一個商品類的引用,訂單項與商品也存在N-1關聯,用 @ManyToOne修飾,指定商品id爲關聯外鍵,

在XML映射文件版本中,需要使用<key-many-to-one>來解決這種關聯主鍵的成員又關聯其他實體的情況,不過在JPA註解中則直接用 @ManyToOne + @Id即可,可見JPA確實比較簡單,

 1 package map.six11;
 2 
 3 import javax.persistence.CascadeType;
 4 import javax.persistence.Column;
 5 import javax.persistence.Entity;
 6 import javax.persistence.GeneratedValue;
 7 import javax.persistence.GenerationType;
 8 import javax.persistence.Id;
 9 import javax.persistence.JoinColumn;
10 import javax.persistence.ManyToOne;
11 import javax.persistence.Table;
12 
13 @Entity
14 @Tab.Entity;
 6 import javax.persistence.GeneratedValue;
 7 import javax.persistence.GenerationType;
 8 import javax.persistence.Id;
 9 import javax.persistence.JoinColumn;
10 import javax.persistence.ManyToOne;
11 import javax.persistence.Table;
12 
13 @Entity
14 @Table(name="order_item_inf")
15 public class OrderItem implements java.io.Serializable {
16     @ManyToOne(targetEntity=Order.class)
17     @JoinColumn(name="order_id", referencedColumnName="order_id")
18     @Id
19     private Order order;
20     @ManyToOne(targetEntity=Product.class)
21     @JoinColumn(name="product_id", referencedColumnName="product_id")
22     @Id
23     private Product product;
24     @Id
25     private int count;
26     public OrderItem() {}
27     public OrderItem(Order order, Product product, int count) {
28         this.order = order;
29         this.product = product;
30         this.count = count;
31     }
32     public boolean equals(Object obj) {
33         if (this == obj) return true;
34         if (obj != null && obj.getClass() == OrderItem.class) {
35             OrderItem target = (OrderItem)obj;
36             return this.order.equals(target.getOrder())
37                     && this.product.equals(target.getProduct())
38                     && this.count == target.getCount();
39         }
40         return false;
41     }
42     public int hashCode() {
43         return (this.product == null ? 0 : this.product.hashCode()) * 31 * 31 +
44                 (this.order == null ? 0 :this.order.hashCode()) * 31 
45                 +  this.count;
46     }
47     public Order getOrder() {
48         return order;
49     }
50     public void setOrder(Order order) {
51         this.order = order;
52     }
53     public Product getProduct() {
54         return product;
55     }
56     public void setProduct(Product product) {
57         this.product = product;
58     }
59     public int getCount() {
60         return count;
61     }
62     public void setCount(int count) {
63         this.count = count;
64     }
65     
66 }

 

商品類不需要主動與誰管理,因此只是一個普通實體,

 1 package map.six11;
 2 
 3 import
相關文章
相關標籤/搜索