zoukankan      html  css  js  c++  java
  • 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             joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id", unique=true),
    23             inverseJoinColumns=@JoinColumn(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     joinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id"),
    28     inverseJoinColumns=@JoinColumn(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             joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id"),
    27             inverseJoinColumns=@JoinColumn(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     joinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id", unique=true),
    25     inverseJoinColumns=@JoinColumn(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     joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id", unique=true),
    26     inverseJoinColumns=@JoinColumn(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 @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 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="product_inf")
    12 public class Product {
    13     @Id @Column(name="product_id")
    14     @GeneratedValue(strategy=GenerationType.IDENTITY)
    15     private Integer productId;
    16     private String name;
    17     public Product() {}
    18     public Product(String name) {
    19         this.setName(name);
    20     }
    21     public Integer getProductId() {
    22         return productId;
    23     }
    24     public void setProductId(Integer productId) {
    25         this.productId = productId;
    26     }
    27     public String getName() {
    28         return name;
    29     }
    30     public void setName(String name) {
    31         this.name = name;
    32     }
    33 }

    下面是一个测试类,

     1 package map.six11;
     2 
     3 import java.util.Date;
     4 
     5 import org.hibernate.Session;
     6 import org.hibernate.SessionFactory;
     7 import org.hibernate.Transaction;
     8 import org.hibernate.cfg.Configuration;
     9 
    10 public class PersonManager {
    11     public static void main(String[] args) {
    12         Configuration conf = new Configuration().configure();
    13         //conf.addClass(Person.class);
    14         //conf.addClass(School.class);
    15         //conf.addClass(Address.class);
    16         //conf.addAnnotatedClass(Person.class);
    17         //conf.addAnnotatedClass(School.class);
    18         //conf.addAnnotatedClass(Address.class);
    19         conf.addAnnotatedClass(Product.class);
    20         conf.addAnnotatedClass(Order.class);
    21         conf.addAnnotatedClass(OrderItem.class);
    22         SessionFactory sf = conf.buildSessionFactory();
    23         Session sess = sf.openSession();
    24         Transaction tx = sess.beginTransaction();
    25 
    26         Product p1 = new Product("手机");
    27         Product p2 = new Product("电脑");
    28         sess.save(p1);
    29         sess.save(p2);
    30         
    31         Order o = new Order(new Date());
    32         
    33         OrderItem i1 = new OrderItem(o, p1, 3);
    34         OrderItem i2 = new OrderItem(o, p2, 5);
    35         
    36         o.getItems().add(i1);
    37         o.getItems().add(i2);
    38         
    39         sess.save(o);
    40         
    41         sess.save(i1);
    42         sess.save(i2);
    43         
    44         tx.commit();
    45         sess.close();
    46         sf.close();
    47     }
    48 }

    执行测试类,生成了3个表,

    Hibernate生成的SQL如下,

    1 Hibernate: insert into product_inf (name) values (?)
    2 Hibernate: insert into product_inf (name) values (?)
    3 Hibernate: insert into Order_inf (orderDate) values (?)
    4 Hibernate: insert into order_item_inf (product_id, order_id, count) values (?, ?, ?)
    5 Hibernate: insert into order_item_inf (product_id, order_id, count) values (?, ?, ?)

    表数据,

    MariaDB [test]> select * from product_inf;
    +------------+------+
    | product_id | name |
    +------------+------+
    |          1 | 手机 |
    |          2 | 电脑 |
    +------------+------+
    2 rows in set (0.00 sec)
    
    MariaDB [test]> select * from order_item_inf;
    +-------+------------+----------+
    | count | product_id | order_id |
    +-------+------------+----------+
    |     3 |          1 |        1 |
    |     5 |          2 |        1 |
    +-------+------------+----------+
    2 rows in set (0.00 sec)
    
    MariaDB [test]> select * from order_inf;
    +----------+---------------------+
    | order_id | orderDate           |
    +----------+---------------------+
    |        1 | 2017-01-10 16:45:31 |
    +----------+---------------------+
    1 row in set (0.00 sec)

    表结构,

    MariaDB [test]> desc order_item_inf;
    +------------+---------+------+-----+---------+-------+
    | Field      | Type    | Null | Key | Default | Extra |
    +------------+---------+------+-----+---------+-------+
    | count      | int(11) | NO   | PRI | NULL    |       |
    | product_id | int(11) | NO   | PRI | NULL    |       |
    | order_id   | int(11) | NO   | PRI | NULL    |       |
    +------------+---------+------+-----+---------+-------+
    3 rows in set (0.05 sec)

    表关联如下,

     级联操作(持久化的传播性)

    级联操作即一个实体状态改变时(update,delete。。。),与之关联的实体也会自动改变状态,这可以简化编程。

    在Hibernate中,对于两个表的关联有两种实现方式,一种是把关联的表(从表)作为主表的组件,另一种是把从表映射为新的持久化类,

    对于将从表记录映射为主表(持久化类)的组件这种情况,即上一章说的”集合属性的元素是组件“的情形,这些组件的生命周期总是依赖于父对象,Hibernate默认就会启用级联操作。

    而对于将关联的表映射问单独的持久化类的情况,Hibernate默认不启用级联操作,需要手动开启,可以在@OneToMany, @OneToOne, @ManyToMany中使用cascade属性来指定。

    cascade有以下五种可选值,

    CascadeTpye.ALL

    CascadeType.MERAGE

    CascadeType.PERSIST

    CascadeType.REFRESH

    CascadeType.REMOVE

    通常不会在@ManyToOne中使用级联属性,因为这没有任何意义,从表的改变不应该影响到主表。

    除了cascade,还有一种特殊的级联策略,即orphanRemoval属性,如果在@OneToMany等注解中设置了这个属性(true或false),则会删除”孤儿“记录。(所谓孤儿记录,就是当程序通过主表实体切断与从表的关联关系,则从表实体就成了孤儿)、

  • 相关阅读:

    如何找回自己!
    身体锻炼靶心心率!
    圣人言大任之人!
    如何修清净心?(净空老法师法语)
    vim 查询定位!
    深切悼念灾区遇难同胞!
    求后倒零
    植物大战僵尸【二分答案, 加贪心思想】
    植物大战僵尸【二分答案, 加贪心思想】
  • 原文地址:https://www.cnblogs.com/fysola/p/6252334.html
Copyright © 2011-2022 走看看