單向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