正文
Hibernate的关联映射
小程序:扫一扫查出行
【扫一扫了解最新限行尾号】
复制小程序
【扫一扫了解最新限行尾号】
复制小程序
单向N-1关联 <many-to-one>
单向N-1关系,比如多个人对应同一个住址,只需要从人实体端找到对应的住址实体,无须关系某个地址的全部住户。程序在N的一端增加一个属性,该属性引用1的一端的关联实体。
例如下面person实体中的address属性,
package map.six11; public class Person {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
} private Integer id;
private int age;
private String name;
private Address address;
}
Address是一个独立的实体,
package map.six11; public class Address {
private Integer addressId;
private String addressDetail;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
}
在N的一端person实体的映射文件中,用<many-to-one>标签标识关联的属性实体address。
值得注意的是 cascade="all" 这个属性,当实体类有数据更新时,关联的属性类也会更新到数据库,这叫级联行为。
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<many-to-one name="address" cascade="all" class="Address" column="address_id" />
</class>
</hibernate-mapping>
在1的一端,则是一个普通的映射文件,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
</class>
</hibernate-mapping>
下面是一个测试类,
package map.six11; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void testPerson() {
Configuration conf = new Configuration().configure();
//conf.addResource("map.six11/Person.hbm.xml");
conf.addClass(Person.class);
//conf.addResource("map.six11/Address.hbm.xml");
conf.addClass(Address.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); Person p = new Person();
Address a = new Address("广州天河");
p.setName("天王盖地虎");
p.setAge(20);
p.setAddress(a);
sess.persist(p);
Address a2 = new Address("上海虹口");
p.setAddress(a2); tx.commit();
sess.close();
sf.close();
} public static void main(String[] args) {
testPerson();
}
}
测试类中,Address只是一个瞬态的持久类,从未持久化,但是因为在Person实体类的映射文件中设置了cascade="all"属性,因此Address实体也会随着Person实体的更新而发生级联更新。
因此可以看到Hibernate不仅在Person表中插入了记录,而且还创建了Address表并且也插入了记录,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=?
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关联关系。
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<join table="person_address">
<key column="person_id" />
<many-to-one name="address" cascade="all" class="Address"
column="address_id" />
</join> </class>
</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"属性即可,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<many-to-one name="address" cascade="all" class="Address" unique="true" column="address_id" />
</class>
</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>标签而已。
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<join table="person_address">
<key column="person_id" />
<many-to-one name="address" cascade="all" class="Address" unique="true"
column="address_id" />
</join>
</class>
</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的一端正是一个集合。
package map.six11; import java.util.HashSet;
import java.util.Set; public class Person {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
} private Integer id;
private int age;
private String name;
private Set<Address> addresses = new HashSet<>();
}
在1的一端实体类的映射文件中,使用<set> <list> <map>等标签来标识关联的集合属性,用子标签<one-to-many>来表示N的一端的实体类。
1的一端与N的一端两个实体类通过1的一端实体类的主键来关联,即在N的一端数据表address_inf中添加外键person_id,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<set name="addresses" table="address_inf">
<key column="person_id"/>
<one-to-many class="Address" />
</set>
</class>
</hibernate-mapping>
这个例子中,因为只需要从1的一端访问N的一端,因此N的一端不需要做改变,实体类Address和映射文件都不需要改变。(当然在底层,hibernate会修改address_inf表,添加一个外键来关联person_inf).
package map.six11; public class Address {
private Integer addressId;
private String addressDetail;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
}
Address实体映射文件,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
</class>
</hibernate-mapping>
测试类如下,
package map.six11; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
conf.addClass(Person.class);
conf.addClass(Address.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); Person p = new Person();
Address a = new Address("广州天河");
//需要先持久化Address 对象
sess.persist(a);
p.setName("天王盖地虎");
p.setAge(21);
p.getAddresses().add(a);
sess.save(p); Address a2 = new Address("上海虹口");
sess.persist(a2);
p.getAddresses().add(a2); tx.commit();
sess.close();
sf.close();
}
}
执行测试类,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)
上面代码的注解版本,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
@OneToMany(targetEntity=Address.class)
@JoinColumn(name="person_id", referencedColumnName="person_id")
private Set<Address> addresses = new HashSet<>(); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
另外,对于这个单向1-N关联的例子,还有两点需要注意,
1.cascade的用法,可以设置级联更新
在测试类中,我们总是先持久化了Address实体,然后才持久化Person实体,这是为了防止Hibernate报错。
查看Hibernate的日志,我们发现其SQL语句顺序如下,
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: insert into person_inf (age, name) values (?, ?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: update address_inf set person_id=? where address_id=?
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属性,
<set name="addresses" table="address_inf" cascade="all">
<key column="person_id"/>
<one-to-many class="Address" />
</set>
如果是通过注解的方式实现的话,则是,
@OneToMany(targetEntity=Address.class, cascade=CascadeType.ALL)
@JoinColumn(name="person_id", referencedColumnName="person_id")
private Set<Address> addresses = new HashSet<>();
这样就能保证即使没有先显示地持久化Address,只要person有更新,所关联的address也会先持久化了。
添加了cascade属性之后hibernate的日志如下,
Hibernate: insert into person_inf (age, name) values (?, ?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: insert into address_inf (addressDetail) values (?)
Hibernate: update address_inf set person_id=? where address_id=?
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
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<set name="addresses" table="person_address">
<key column="person_id"/>
<many-to-many class="Address" column="AddressID" unique="true" />
</set>
</class>
</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 注解版,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
@OneToMany(targetEntity = Address.class)
@JoinTable(name = "person_address", joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "person_id"),
inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "address_id", unique = true))
private Set<Address> addresses = new HashSet<>(); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
注意XML映射文件版与JPA注解版的区别,在映射文件中,是通过使用<many-to-many unique="true">标签来映射1-N,而在JPA注解版中,可以直接使用@OneToMany注解了,从语义上来说,注解版更加直观。
单项N-N关联
单向的N-N关联和1-N关联的持久化类完全一样,都是在控制关系的一端增加集合属性(Set),被关联的实体则以集合形式存在。N-N关联必须使用连接表,而且与使用连接表的单向1-N关联非常的相似,只是去掉了unique="true"的限制。映射文件如下,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<set name="addresses" table="person_address">
<key column="person_id"/>
<many-to-many class="Address" column="AddressID" />
</set>
</class>
</hibernate-mapping>
执行结果与1-N关联一模一样,只不过生成的person_address表的addressID字段不再有唯一约束。
无连接表的双向1-N关联
双向1-N关联不仅对于实体类需要增加管理属性(集合),在关联实体中也需要增加外键,因此实体类和关联类都需要修改。同时,Hibernate建议不要在1的一端控制关系,因此要在1的一端的映射文件加inverse="true"属性(JPA注解版则是加mappedBy)属性。
Person实体类,注意加粗的关联集合,
package map.six11; import java.util.HashSet;
import java.util.Set; public class Person {
private Integer id;
private int age;
private String name;
private Set<Address> addresses = new HashSet<>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
映射文件,指定关联实体类,及关联的外键person_id,同时设置inverse=true属性表明1的一端不再控制关系。
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<set name="addresses" inverse="true">
<key column="person_id"/>
<one-to-many class="Address" />
</set>
</class>
</hibernate-mapping>
关联的实体类Address, 注意加粗的实体类引用,
package map.six11; public class Address {
private Integer addressId;
private String addressDetail;
private Person person;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
Address的映射文件,通过many-to-one>关联引用实体类,也需要指定外键,并且必须和Person指定的是同一个外键。
设置not-null可以保证每条从表记录(address_inf)都有与之对应的主表(person_inf)记录。
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
<!-- 双向1-N关联的关联实体中,必须指定person_id且与实体类的Set属性中的key column相同 -->
<many-to-one name="person" class="Person" column="person_id" not-null="true" />
</class>
</hibernate-mapping>
测试类如下,注意总是先持久化实体类,再设置关联类与持久类关系,最后持久化关联类,
package map.six11; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
conf.addClass(Person.class);
conf.addClass(Address.class);
//conf.addAnnotatedClass(Person.class);
//conf.addAnnotatedClass(Address.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); //先持久化主表
Person p = new Person();
p.setName("天王盖地虎");
p.setAge(21);
sess.save(p); Address a = new Address("广州天河");
//先设置关联,再持久化a
a.setPerson(p);
sess.persist(a); Address a2 = new Address("上海虹口");
//先设置关联,再持久化a
a2.setPerson(p);
sess.persist(a2); tx.commit();
sess.close();
sf.close();
}
}
执行测试类,发现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"表明不再控制关系,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
@OneToMany(targetEntity=Address.class, mappedBy="person")
private Set<Address> addresses = new HashSet<>(); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
关联实体类,为关联实体person设置@ManyToOne注解并设置person_id为外键。
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table; @Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
private String addressDetail;
@ManyToOne(targetEntity=Person.class)
@JoinColumn(name="person_id", referencedColumnName="person_id", nullable=false)
private Person person;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
对于双向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映射文件,
<set name="addresses" inverse="true" table="person_address">
<key column="person_id" />
<many-to-many class="Address" column="address_id" unique="true" />
</set>
Address映射文件,
<join table="person_address">
<key column="address_id" />
<many-to-one name="person" column="person_id" not-null="true" />
</join>
完整代码如下,
person实体类,添加集合属性 addresses,
package map.six11; import java.util.HashSet;
import java.util.Set; public class Person {
private Integer id;
private int age;
private String name;
private Set<Address> addresses = new HashSet<>(); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
映射文件,指定连接表和关联类及关联字段
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<set name="addresses" inverse="true" table="person_address">
<key column="person_id" />
<many-to-many class="Address" column="address_id" unique="true" />
</set>
</class>
</hibernate-mapping>
Address实体类,添加Person实体类的引用,
package map.six11; public class Address {
private Integer addressId;
private String addressDetail;
private Person person;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
映射文件,指定连接表和关联类及关联字段,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
<join table="person_address">
<key column="address_id" />
<many-to-one name="person" column="person_id" not-null="true" />
</join>
</class>
</hibernate-mapping>
测试类,
package map.six11; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
conf.addClass(Person.class);
conf.addClass(Address.class);
//conf.addAnnotatedClass(Person.class);
//conf.addAnnotatedClass(Address.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); //先持久化主表
Person p = new Person();
p.setName("天王盖地虎");
p.setAge(21);
sess.save(p); Address a = new Address("广州天河");
//先设置关联,再持久化a
a.setPerson(p);
sess.persist(a); Address a2 = new Address("上海虹口");
//先设置关联,再持久化a
a2.setPerson(p);
sess.persist(a2); tx.commit();
sess.close();
sf.close();
}
}
执行测试类,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实体类,
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table; @Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
private String addressDetail;
@ManyToOne(targetEntity=Person.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id", unique=true),
inverseJoinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id")
)
private Person person;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
执行结果与前面一样。
双向N-N关联
双向N-N关联只能用连接表实现。在关联实体两边都添加Set集合属性,在映射文件中都使用<Set>集合标签关联连接表,都使用<key>标签标识连接表外键(实体类主键),都使用<many-to-many>表示被关联实体类并指明关联的字段,都不能使用unique=true属性因为是多对多关联。
Person实体类映射文件,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<set name="addresses" inverse="true" table="person_address">
<key column="person_id" />
<many-to-many class="Address" column="address_id" />
</set>
</class>
</hibernate-mapping>
Address实体类映射文件,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
<join table="person_address">
<key column="address_id" />
<many-to-one name="person" column="person_id" not-null="true" />
</join>
</class>
</hibernate-mapping>
下面是JPA注解版,
Person实体,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
@ManyToMany(targetEntity=Address.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id"),
inverseJoinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id"))
private Set<Address> addresses = new HashSet<>(); public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
Address实体,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table; @Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
private String addressDetail;
@ManyToMany(targetEntity=Person.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id"),
inverseJoinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id")
)
private Set<Person> person = new HashSet<>();
public Set<Person> getPerson() {
return person;
}
public void setPerson(Set<Person> person) {
this.person = person;
}
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
}
双向1-1关联 (基于外键)
通过在表中添加外键列实现1-1关联,外键可以添加在任意一边表中,映射文件则添加<many-to-one unique="true">标签来关联另一个实体,未添加外键的实体映射文件则用<one-to-one>来关联实体,关键代码如下。
现在假如将外键列添加在address_inf表中,
则Person映射文件为,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<one-to-one name="address" property-ref="person"/>
</class>
</hibernate-mapping>
Address映射文件为,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
<many-to-one name="person" unique="true" column="person_id" not-null="true" />
</class>
</hibernate-mapping>
如果用JPA注解实现,则Person实体类为,
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
@OneToOne(targetEntity=Address.class, mappedBy="person")
private Address address; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address实体类如下,两边实体类都使用的是@OneToOne, 这更符合语意。 在Address这边,显示地指明了关联外键列。
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table; @Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
private String addressDetail;
@OneToOne(targetEntity=Person.class)
@JoinColumn(name="person_id", referencedColumnName="person_id", unique=true)
private Person person; public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
双向1-1关联 (基于主键)
基于主键的双向1-1关联,两边都使用<one-to-one>关联另对方实体类,只不过需要在其中一个实体类中,将主键生成器设置为foreign,使其变成从表,
比如在Address映射文件中,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="foreign" >
<param name="property">person</param>
</generator>
</id>
<property name="addressDetail" />
<one-to-one name="person" />
</class>
</hibernate-mapping>
在Person映射文件中,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<one-to-one name="address" />
</class>
</hibernate-mapping>
双向1-1关联 (基于连接表)
Hibernate并不推荐基于连接表的双向1-1关联,因为映射比较复杂。
在实体两边,都使用<join>标签指定连接表,都使用<many-to-one>映射关联类。
Person映射文件如下,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<join table="person_address" inverse="true">
<key column="person_id" unique="true" />
<many-to-one name="address" class="Address" column="address_id" unique="true" />
</join>
</class>
</hibernate-mapping>
Address映射文件如下,
<?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="Address" table="address_inf">
<id name="addressId" column="address_id">
<generator class="identity" />
</id>
<property name="addressDetail" />
<join table="person_address" optional="true">
<key column="person_id" unique="true" />
<many-to-one name="person" class="Person" column="person_id"
unique="true" />
</join>
</class>
</hibernate-mapping>
两个映射文件的区别是,Address的address_id将作为连接表的主键,因此Address映射文件为<join 指定optional=true属性,而Person则为<join指定inverse=true属性。
下面是JPA注解实现,两边都是用@OneToOne注解映射关联类,两边都使用@JoinTable表示连接表,两个实体类写法都是一样的,只是互相引用对方而已,
Person实体类,
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
@OneToOne(targetEntity=Address.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id", unique=true),
inverseJoinColumns=@JoinColumn(name="access_id", referencedColumnName="address_id", unique=true)
)
private Address address; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address实体类,
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table; @Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
private String addressDetail;
@OneToOne(targetEntity=Person.class)
@JoinTable(name="person_address",
joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id", unique=true),
inverseJoinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id", unique=true)
)
private Person person; public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
组件属性包含关联实体
如果一个持久化类Person的一个组件属性Address中,又有一个属性School也是一个持久化类,那么从逻辑上来讲,Address和School应该是关联的,但是如果Address仅仅是一个组件而不是实体类,那么就会变成Person和School关联。
比如下面这样,Person类中有一个组件属性Address,
package map.six11; import java.util.HashSet;
import java.util.Set; public class Person {
private Integer id;
private int age;
private String name;
private Address address; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
组件Address(注意这里只当作组件用,不再当作持久化了类, 因此也不会有映射文件)
package map.six11; import java.util.HashSet;
import java.util.Set; public class Address {
private Integer addressId;
private String addressDetail;
private Person person;
private Set<School> schools = new HashSet<>();
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Set<School> getSchools() {
return schools;
}
public void setSchools(Set<School> schools) {
this.schools = schools;
}
}
在组件属性Address中包含了一个集合属性school, shool又是一个持久化类,代码如下,
package map.six11; public class School {
private Integer schoolId;
private String schoolName; public String getSchoolName() {
return schoolName;
} public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
} public School(){} public School(String schoolName) {
this.schoolName = schoolName;
} public Integer getSchoolId() {
return schoolId;
} public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
}
可见,从逻辑上来说,Address与School形成1-N的单向关联。
Person映射文件如下,
<?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">
<generator class="identity" />
</id>
<property name="age" type="int" />
<property name="name" type="string" />
<!-- 映射主键元素 -->
<component name="address" class="Address">
<!-- 映射组件的包含实体 -->
<parent name="person" />
<property name="addressDetail" />
<!-- 映射组件类的集合属性, 集合元素又是其他持久化实体类 -->
<set name="schools">
<!-- 外键 -->
<key column="address_id" />
<!-- 1-N关联 -->
<one-to-many class="School" />
</set>
</component>
</class>
</hibernate-mapping>
Address不是持久化类,没有映射文件。 School映射文件如下,
<?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="School" table="school_inf">
<id name="schoolId" column="school_id">
<generator class="identity" />
</id>
<property name="schoolName" />
</class>
</hibernate-mapping>
测试类如下,
package map.six11; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
conf.addClass(Person.class);
conf.addClass(School.class);
//conf.addClass(Address.class);
//conf.addAnnotatedClass(Person.class);
//conf.addAnnotatedClass(Address.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); //先持久化主表
Person p = new Person();
p.setName("天王盖地虎");
p.setAge(21);
sess.save(p); Address a = new Address("广州天河");
p.setAddress(a); School s1 = new School("北京大学");
School s2 = new School("清华大学"); sess.save(s1);
sess.save(s2); a.getSchools().add(s1);
a.getSchools().add(s2); tx.commit();
sess.close();
sf.close();
}
}
测试类中,我们只需要加载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注解版,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private int age;
private String name;
private Address address; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany; import org.hibernate.annotations.Parent; @Embeddable
public class Address {
private Integer addressId;
private String addressDetail;
@Parent
private Person person;
@OneToMany(targetEntity=School.class)
@JoinColumn(name="address_id", referencedColumnName="person_id")
private Set<School> schools = new HashSet<>();
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Set<School> getSchools() {
return schools;
}
public void setSchools(Set<School> schools) {
this.schools = schools;
}
}
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table; @Entity
@Table(name="school_inf")
public class School {
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer schoolId;
private String schoolName; public String getSchoolName() {
return schoolName;
} public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
} public School(){} public School(String schoolName) {
this.schoolName = schoolName;
} public Integer getSchoolId() {
return schoolId;
} public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
}
基于复合主键的关联关系
虽然Hibernate不推荐复合主键,但依然提供了支持。下面的例子中,Person将会使用复合主键,在Person中有个Address集合属性,其元素也是持久化实体,同时还在Address端添加了Person的引用,因此Person与Address之间形成双向1-N关联关系。
Person类,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="person_inf")
public class Person implements java.io.Serializable{
@Id
private String first;
@Id
private String last;
private int age;
@OneToMany(targetEntity=Address.class, mappedBy="person", cascade=CascadeType.ALL)
private Set<Address> addresses = new HashSet<>(); public int getAge() {
return age;
}
public Set<Address> getAddresses() {
return addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
public void setAge(int age) {
this.age = age;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public boolean equals(Object obj) {
if (this==obj) return true;
if (obj != null && obj.getClass() == Person.class) {
Person target = (Person)obj;
return target.getFirst().equals(this.first) && target.getLast().equals(this.last);
}
return false;
}
public int hashCode() {
return getFirst().hashCode() * 31 + getLast().hashCode();
}
}
Address实体,
package map.six11; import java.util.HashSet;
import java.util.Set; import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.Table; import org.hibernate.annotations.Parent; @Entity
@Table(name="address_inf")
public class Address implements java.io.Serializable {
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer addressId;
private String addressDetail; @ManyToOne(targetEntity=Person.class)
@JoinColumns({@JoinColumn(name="person_first", referencedColumnName="first", nullable=false),
@JoinColumn(name="person_last", referencedColumnName="last", nullable=false)})
private Person person;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(String addressDetail) {
this.addressDetail = addressDetail;
}
public Address() {}
public Address(String addressDetail) {
this.addressDetail = addressDetail;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
测试类,
package map.six11; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
//conf.addClass(Person.class);
//conf.addClass(School.class);
//conf.addClass(Address.class);
conf.addAnnotatedClass(Person.class);
//conf.addAnnotatedClass(School.class);
conf.addAnnotatedClass(Address.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); Person p = new Person();
p.setFirst("天王盖地虎");
p.setLast("宝塔镇河妖");
p.setAge(21); Address a = new Address("广州天河");
a.setPerson(p);
Address a2 = new Address("上海虹桥");
a2.setPerson(p); p.getAddresses().add(a);
p.getAddresses().add(a2);
sess.save(p); tx.commit();
sess.close();
sf.close();
}
}
因为在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中就需要做一些特殊的映射了。
下面是订单类,
package map.six11; import java.util.Date;
import java.util.HashSet;
import java.util.Set; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity
@Table(name="Order_inf")
public class Order {
@Id @Column(name="order_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer orderId;
private Date orderDate;
@OneToMany(targetEntity=OrderItem.class, mappedBy="order")
private Set<OrderItem> items = new HashSet<>();
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
public Set<OrderItem> getItems() {
return items;
}
public void setItems(Set<OrderItem> items) {
this.items = items;
}
public Order() {}
public Order(Date orderDate) {
this.orderDate = orderDate;
}
}
订单类与订单项存在1-N双向关联,所以在订单类中有一个Set存放订单项,用@OneToMany与之关联并通过mappedBy设置订单类不控制关系。
与之对应的是在订单项类中,有一个订单类的引用,订单项与订单存在N-1关联,用@ManyToOne修饰,指定订单id为关联外键,
同时还需要有一个商品类的引用,订单项与商品也存在N-1关联,用 @ManyToOne修饰,指定商品id为关联外键,
在XML映射文件版本中,需要使用<key-many-to-one>来解决这种关联主键的成员又关联其他实体的情况,不过在JPA注解中则直接用 @ManyToOne + @Id即可,可见JPA确实比较简单,
package map.six11; import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table; @Entity
@Table(name="order_item_inf")
public class OrderItem implements java.io.Serializable {
@ManyToOne(targetEntity=Order.class)
@JoinColumn(name="order_id", referencedColumnName="order_id")
@Id
private Order order;
@ManyToOne(targetEntity=Product.class)
@JoinColumn(name="product_id", referencedColumnName="product_id")
@Id
private Product product;
@Id
private int count;
public OrderItem() {}
public OrderItem(Order order, Product product, int count) {
this.order = order;
this.product = product;
this.count = count;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj != null && obj.getClass() == OrderItem.class) {
OrderItem target = (OrderItem)obj;
return this.order.equals(target.getOrder())
&& this.product.equals(target.getProduct())
&& this.count == target.getCount();
}
return false;
}
public int hashCode() {
return (this.product == null ? 0 : this.product.hashCode()) * 31 * 31 +
(this.order == null ? 0 :this.order.hashCode()) * 31
+ this.count;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
} }
商品类不需要主动与谁管理,因此只是一个普通实体,
package map.six11; import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table; @Entity
@Table(name="product_inf")
public class Product {
@Id @Column(name="product_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer productId;
private String name;
public Product() {}
public Product(String name) {
this.setName(name);
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
下面是一个测试类,
package map.six11; import java.util.Date; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void main(String[] args) {
Configuration conf = new Configuration().configure();
//conf.addClass(Person.class);
//conf.addClass(School.class);
//conf.addClass(Address.class);
//conf.addAnnotatedClass(Person.class);
//conf.addAnnotatedClass(School.class);
//conf.addAnnotatedClass(Address.class);
conf.addAnnotatedClass(Product.class);
conf.addAnnotatedClass(Order.class);
conf.addAnnotatedClass(OrderItem.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction(); Product p1 = new Product("手机");
Product p2 = new Product("电脑");
sess.save(p1);
sess.save(p2); Order o = new Order(new Date()); OrderItem i1 = new OrderItem(o, p1, 3);
OrderItem i2 = new OrderItem(o, p2, 5); o.getItems().add(i1);
o.getItems().add(i2); sess.save(o); sess.save(i1);
sess.save(i2); tx.commit();
sess.close();
sf.close();
}
}
执行测试类,生成了3个表,
Hibernate生成的SQL如下,
Hibernate: insert into product_inf (name) values (?)
Hibernate: insert into product_inf (name) values (?)
Hibernate: insert into Order_inf (orderDate) values (?)
Hibernate: insert into order_item_inf (product_id, order_id, count) values (?, ?, ?)
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),则会删除”孤儿“记录。(所谓孤儿记录,就是当程序通过主表实体切断与从表的关联关系,则从表实体就成了孤儿)、