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