映射一对多关联关系
- 在领域模型中,类与类之间最普遍的关系就是关联关系。
- 在UML中,关联是有方向的。
以Customer
和Order
为例:一个用户能发出多个订单,而一个订单只能属于一个客户,从Order
到Customer
的关联是多对一的关联,而从Customer
到Order
是一对多的关联。
单向n-1
- 单向n-1关联只需从n端可以访问1端。
- 域模型:从
Order
到Customer
的多对一单向关联需要在Order
类中定义一个Customer
属性,而在Customer
类中无需定义存放Order
对象的集合属性。 - 关系数据模型:orders表中的customer_id参照customer表的主键。
- 在Hibernate映射文件中使用
<many-to-one>
标签可以来映射多对一的关联关系,其标签有如下属性:
name
:一的那一端的属性的名字
class
:一的那一端对应的类名
column
:一的那一端在多的一端中对应的数据表中的外键的名字
在此以Order和Customer为例,Order与Customer为多对一关系。首先我们建立Order
类和Customer
类,如下:
package com.cerr.hibernate.n21;
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer orderId) {
this.orderId = orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
package com.cerr.hibernate.n21;
public class Customer {
private Integer customerId;
private String customerName;
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}
IDEA只支持由数据表映射出实体类和映射文件,而我们此时是要通过实体类和映射文件来生成数据表,因此映射文件应该我们自己来新建。因此我们新建Customer.hbm.xml
文件和Order.hbm.xml
文件。映射信息如下:
Customer.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.Customer" table="customers" schema="hibernate5" dynamic-update="true">
<id name="customerId" column="customer_id">
<generator class="native" />
</id>
<property name="customerName" column="customer_name"/>
</class>
</hibernate-mapping>
Order.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.Order" table="orders" schema="hibernate5" dynamic-update="true">
<id name="orderId" column="order_id">
<generator class="native" />
</id>
<property name="orderName" column="order_name"/>
<!-- 映射多对一的关联关系 -->
<many-to-one name="customer" class="com.cerr.hibernate.n21.Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
保存操作的测试类:
package com.cerr.hibernate.n21;
import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test4(){
Customer customer = new Customer();
customer.setCustomerName("aa");
Order order = new Order();
order.setOrderName("1");
Order order1 = new Order();
order1.setOrderName("2");
//设定关联关系
order.setCustomer(customer);
order1.setCustomer(customer);
//先插入1的一端,再插入n的一端。只有三条insert语句。
session.save(customer);
session.save(order);
session.save(order1);
//如果先插入n的一端,再插入1的一端,则会多出来num(n的一端)条update语句。
// session.save(order);
// session.save(order1);
// session.save(customer);
}
}
在测试类中我们分别新建了两个Order
类和一个Customer
类,并且在调用save()
时我们使用了两种情况,一种情况是先保存Customer
类(即先保存1),再保存Order
类(再保存n)。另一种是先保存Order
类(先保存n),再保存Customer
类(再保存1)。两种结果都会插入成功,但是第一种的话只会生成3条INSERT语句,但是第二种会生成3条INSERT语句和2条UPDATE语句。因为先插入n的一端时,无法确定1的一端的外键值,所以只能先进行插入,然后等1的一端插入之后,再额外的发送UPDATE去修改n的一端的外键属性。
所以在插入的时候我们推荐先插入1的那一端,再插入n的那一端。
生成的数据表如下:
查询操作的测试类:
package com.cerr.hibernate.n21;
import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test5(){
//若查询n的一端的对象,默认情况下只查询n的一端的对象,而没有查询1的那一端的对象
Order order = session.get(Order.class,1);
System.out.println(order);
//在需要使用关联的对象时,才会发送对应的sql语句。
Customer customer = order.getCustomer();
System.out.println(customer);
}
}
会发现若查询n的一端的对象,默认情况下只查询n的一端的对象,而没有查询1的那一端的对象,只有在需要使用到关联对象时,才会发送对应的sql语句,而如果在查询n端之后把session
关了,才来使用关联对象(Customer
)会导致LazyInitializationException异常
。
在获取Order
对象后,打印其关联的Customer
对象,发现其关联的Customer
对象是一个代理对象。
使用删除操作的话,在没有设定级联关系的情况下,且1端的对象有n端对象在引用它(即存在n-1的映射关系),不能直接删除1端的对象。
双向1-n
双向1-n与双向n-1是完全相同的两种情形。双向1-n需要在1端可以访问n端,反之亦然。
- 域模型:以
Order
和Customer
为例,从Order
到Customer
的多对一双向关联需要在Order
类中定义一个Customer
属性,而在Customer
类中需定义存放Order
对象的集合属性。 - 关系数据模型:orders表中的customer_id参照customer表的主键。
我们在上述的Order
类和Customer
类中修改,因为此时一端需要有一个Set集合来维护n端的Order
,因此在Customer
类中多出一个属性也就是一个维护n端的集合。
对于这个集合要注意两点:
- 对于这个
Set
集合类型,在声明时,要使用接口类型,因为hibernate在获取集合类型时,返回的是hibernate内置的集合类型,而不是javase的标准集合实现。 - 为了防止空指针异常,在声明集合的时候就要把集合给初始化。
Customer
类如下:
package com.cerr.hibernate.n21.both;
import java.util.HashSet;
import java.util.Set;
public class Customer {
private Integer customerId;
private String customerName;
//维护Order的集合
//需要把集合做初始化,防止出现空指针异常。
private Set<Order> orders = new HashSet <>();
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Set < Order > getOrders() {
return orders;
}
public void setOrders(Set < Order > orders) {
this.orders = orders;
}
}
而Order
类没有改变,Order.hbm.xml
配置文件如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.both.Order" table="orders" schema="hibernate5" dynamic-update="true">
<id name="orderId" column="order_id">
<generator class="native" />
</id>
<property name="orderName" column="order_name"/>
<!-- 映射关系 -->
<many-to-one name="customer" class="com.cerr.hibernate.n21.both.Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
对于映射文件新增的<set>
标签的属性:
-
table
:该值要和n-1中的n端的数据表名字一致。 -
<key>子标签
:指定n的表中的外键列的名字 -
inverse
:指定由哪一方来维护关联关系,通常在一端的映射文件中设置为true
,表明由n端来维护关联关系。 -
cascade
:设置级联操作。 -
order-by
:在查询时对集合中的元素进行排序,order-by
中使用的是表的字段名,而不是持久化类的属性名。
在Customer.hbm.xml
中有了修改,此时要增加一个集合属性,不过该版本还不算比较完善,文件如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.both.Customer" table="customers" schema="hibernate5" dynamic-update="true">
<id name="customerId" column="customer_id">
<generator class="native" />
</id>
<property name="customerName" column="customer_name"/>
<!-- 映射1对多的那个集合属性 -->
<set name="orders" table="orders" cascade="delete">
<key column="customer_id"></key>
<one-to-many class="com.cerr.hibernate.n21.both.Order"/>
</set>
</class>
</hibernate-mapping>
保存操作的测试类:
package com.cerr.hibernate.n21.both;
import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test4(){
Customer customer = new Customer();
customer.setCustomerName("aa");
Order order = new Order();
order.setOrderName("1");
Order order1 = new Order();
order1.setOrderName("2");
//设定关联关系
order.setCustomer(customer);
order1.setCustomer(customer);
customer.getOrders().add(order);
customer.getOrders().add(order1);
//先插入1的一端,再插入n的一端。三条insert语句和2条UPDATE。
session.save(customer);
session.save(order);
session.save(order1);
//如果先插入n的一端,再插入1的一端,则会多出来2条update语句。合计三条INSERT语句,4条update
// session.save(order);
// session.save(order1);
// session.save(customer);
}
}
在save()
时,发现如果先插入一端,再插入n端的话,会发送三条INSERT语句和两条UPDATE语句,因为此时一端和n端都维护关联关系,所以会多出UPDATE语句。可以在1端的<set>
节点中指定inverse=true
来使一端放弃维护关联关系。
补充后的Customer.hbm.xml
文件如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n21.both.Customer" table="customers" schema="hibernate5" dynamic-update="true">
<id name="customerId" column="customer_id">
<generator class="native" />
</id>
<property name="customerName" column="customer_name"/>
<!-- 映射1对多的那个集合属性 -->
<set name="orders" table="orders" inverse="true">
<key column="customer_id"></key>
<one-to-many class="com.cerr.hibernate.n21.both.Order"/>
</set>
</class>
</hibernate-mapping>
对于查询操作,查n端的情况如上述单向n-1的情况一样。我们来看查一端的。
测试类:
@org.junit.Test
public void test6(){
//对n端的集合使用延迟加载
Customer customer = session.get(Customer.class,1);
System.out.println(customer.getCustomerName());
//集合返回的是Hibernate内置的集合类型
//该类型具有延迟加载和存放代理对象的功能。
System.out.println(customer.getOrders().getClass().getName());
}
查询一端时,如果没有用到一端中关联n端的集合的话,它也只会去查一端的对应表,而不会查n端的对应的数据表,因此对n端也是使用延迟加载,再需要用到那个关联n端的集合时才会去查表。并且该集合返回的类型是Hibernate内置的集合类型,该类型具有延迟加载和存放代理对象的功能。在关闭session
后再去获取集合时也会发生LazyInitializationException异常
。
在删除时也要注意级联操作,跟上面的单向n-1一样。
映射一对一关联关系
- 域模型
以部门Department
和经理Manager
为例,假设一个部门只有一个经理,一个经理只能管一个部门,这样就形成了一个一对一的关联关系。 - 关系数据模型
有两种。一种是按照外键映射,另一种是按照主键映射。
基于外键映射的一对一关联关系
对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键的一端增加<many-to-one>
元素,为<many-to-one>
元素添加一个unique=true
属性来表示为1-1关联。
另一端需要使用<one-to-one>
元素,该元素使用property-ref
属性指定使用被关联实体主键以外的字段作为关联属性。
- 没有使用该属性的SQL语句:
select
manager0_.mgr_id as mgr_id1_1_0_,
manager0_.mgr_name as mgr_name2_1_0_,
department1_.dept_id as dept_id1_0_1_,
department1_.dept_name as dept_nam2_0_1_,
department1_.mgr_id as mgr_id3_0_1_
from
manager manager0_
left outer join
//这里错了
departments department1_
on manager0_.mgr_id=department1_.dept_id
where
manager0_.mgr_id=?
- 使用该属性后的SQL:
select
manager0_.mgr_id as mgr_id1_1_0_,
manager0_.mgr_name as mgr_name2_1_0_,
department1_.dept_id as dept_id1_0_1_,
department1_.dept_name as dept_nam2_0_1_,
department1_.mgr_id as mgr_id3_0_1_
from
manager manager0_
left outer join
//这里没错
departments department1_
on manager0_.mgr_id=department1_.mgr_id
where
manager0_.mgr_id=?
对于上述的例子,我们首先新建Department
类和Manager
类,如下:
package com.cerr.hibernate.one2one.foreign;
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public Manager getMgr() {
return mgr;
}
public void setMgr(Manager mgr) {
this.mgr = mgr;
}
}
package com.cerr.hibernate.one2one.foreign;
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
public Integer getMgrId() {
return mgrId;
}
public void setMgrId(Integer mgrId) {
this.mgrId = mgrId;
}
public String getMgrName() {
return mgrName;
}
public void setMgrName(String mgrName) {
this.mgrName = mgrName;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
}
编写映射文件
Department.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.cerr.hibernate.one2one.foreign.Department" table="departments" schema="hibernate5" dynamic-update="true">
<id name="deptId" column="dept_id">
<generator class="native" />
</id>
<property name="deptName" column="dept_name"/>
<!-- 使用many-to-one的方式来映射1-1关联关系-->
<many-to-one name="mgr" class="com.cerr.hibernate.one2one.foreign.Manager"
column="mgr_id" unique="true"></many-to-one>
</class>
</hibernate-mapping>
Manager.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.cerr.hibernate.one2one.foreign.Manager" table="manager" schema="hibernate5" dynamic-update="true">
<id name="mgrId" column="mgr_id">
<generator class="native" />
</id>
<property name="mgrName" column="mgr_name"/>
<!-- 映射1-1关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="dept" class="com.cerr.hibernate.one2one.foreign.Department"
property-ref="mgr"></one-to-one>
</class>
</hibernate-mapping>
在hibernate.cfg.xml
中加入配置:
<mapping resource="com/cerr/hibernate/one2one/foreign/Manager.hbm.xml"/>
<mapping resource="com/cerr/hibernate/one2one/foreign/Department.hbm.xml"/>
测试类文件:
package com.cerr.hibernate.one2one.foreign;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test1(){
Department department = session.get(Department.class,1);
Manager manager = department.getMgr();
System.out.println(manager.getMgrName());
}
@org.junit.Test
public void test(){
Department department = new Department();
department.setDeptName("AA");
Manager manager = new Manager();
manager.setMgrName("aa");
//设定关联关系
manager.setDept(department);
department.setMgr(manager);
//保存操作
session.save(manager);
session.save(department);
}
}
在查询有外键的实体对象时,使用的依旧是懒加载。其关联对象在需要使用时才初始化。
而在查询没有外键的实体对象时,发现使用的是左外连接查询,一并查询出其关联的对象并进行初始化。
基于主键映射的一对一关联关系
基于主键的映射策略指一端的生成器使用foreign
策略,表明根据对方的主键来生成自己的主键,自己并不能独立生成主键。<param>
子元素指定使用当前持久化类的哪个属性作为对方。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(对方)所对应的数据库表主键。
采用foreign
主键生成器策略的一端增加one to one
元素映射关联属性,其one to one
属性还应增加constrained=true
属性,另一端增加one to one
元素映射关联属性。
我们在上述的Demo中进行修改,首先因为都是一对一的关联关系,因此两个实体类是不需要修改的,要修改的是映射文件,修改后的映射文件如下:
Department.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.cerr.hibernate.one2one.primary.Manager" table="manager" schema="hibernate5" dynamic-update="true">
<id name="mgrId" column="mgr_id">
<generator class="native" />
</id>
<property name="mgrName" column="mgr_name"/>
<!-- 映射1-1关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="dept" class="com.cerr.hibernate.one2one.primary.Department"></one-to-one>
</class>
</hibernate-mapping>
Manager.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.cerr.hibernate.one2one.primary.Manager" table="manager" schema="hibernate5" dynamic-update="true">
<id name="mgrId" column="mgr_id">
<generator class="native" />
</id>
<property name="mgrName" column="mgr_name"/>
<!-- 映射1-1关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="dept" class="com.cerr.hibernate.one2one.primary.Department"></one-to-one>
</class>
</hibernate-mapping>
映射单向多对多关联关系
单向n-n的关联必须使用连接表。
首先我们先建立两个实体类:
package com.cerr.hibernate.n2n;
import java.util.HashSet;
import java.util.Set;
public class Category {
private Integer id;
private String name;
private Set<Item> items = new HashSet <>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set < Item > getItems() {
return items;
}
public void setItems(Set < Item > items) {
this.items = items;
}
}
package com.cerr.hibernate.n2n;
public class Item {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
对于映射文件:
与1-n映射类似,必须为set集合元素添加key子元素,指定categories_items表中参照categories表的外键为c_id
,与1-n关联映射不同的是,建立n-n关联时,集合中的元素使用<many-to-many>
,<many-to-many>
子元素的class
属性指定items
集合中存放的是Item对象,<many-to-many>
子元素的column
属性指定categories_items
表中参照items表的外键为i_id
。
Category.hbm.xml
文件如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n2n.Category" table="categories" schema="hibernate5" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
<!-- table:指定中间表 -->
<set name="items" table="categories_items">
<key>
<column name="c_id"/>
</key>
<!-- 使用many-to-many指定多对多的关联关系,column指定set集合中的持久化类在中间表的外键列的名称-->
<many-to-many class="com.cerr.hibernate.n2n.Item" column="i_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
Item.hbm.xml
文件如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n2n.Item" table="items" schema="hibernate5" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
</class>
</hibernate-mapping>
测试类:
package com.cerr.hibernate.n2n;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import java.util.Set;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@org.junit.Test
public void test1(){
Category category = session.get(Category.class,3);
System.out.println(category.getName());
Set<Item> items = category.getItems();
System.out.println(items.size());
}
@org.junit.Test
public void test(){
Category category = new Category();
category.setName("AA");
Category category1 =new Category();
category1.setName("BB");
Item item = new Item();
Item item1 = new Item();
item.setName("aa");
item1.setName("bb");
//设定关联关系
category.getItems().add(item);
category.getItems().add(item1);
category1.getItems().add(item);
category1.getItems().add(item1);
//执行保存操作
session.save(category);
session.save(category1);
session.save(item);
session.save(item1);
}
}
映射双向多对多关联关系
双向n-n关联需要两端都使用集合属性,并且需要使用连接表。
集合属性应增加
key
子元素用以映射外键列,集合元素里还应增加many-to-many
子元素关联实体类。在双向n-n关联的两边都需指定连接表的表名及外键列的列名,两个集合元素的
set
的table
元素的值必须指定,而且必须相同,该值是连接表的表名。set
元素的两个子元素:key
和many-to-many
都必须指定column
属性,其中key
和many-to-many
分别指定本持久化类和关联类在连接表中的外键列名,因为两边的key
与many-to-many
的column
属性交叉相同。
例如一个边的set
元素的key
的column
值为a,many-to-many
的column
为b;则另一边的set
元素的key
的column
值为b,many-to-many
的column
值为a。对于双向n-n关联,必须把其中一端的
inverse
设置为true
,否则两端都维护关联关系可能会造成主键冲突。
我们在上面实体类和映射文件的基础上改,需要在Item
类中添加一个集合
package com.cerr.hibernate.n2n;
import java.util.HashSet;
import java.util.Set;
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet <>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set < Category > getCategories() {
return categories;
}
public void setCategories(Set < Category > categories) {
this.categories = categories;
}
}
再更改其映射文件Item.hbm.xml
,需要添加<set>
元素
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.n2n.Item" table="items" schema="hibernate5" dynamic-update="true">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
<!-- 指定中间表 -->
<set name="categories" table="categories_items" inverse="true">
<key column="i_id"></key>
<many-to-many class="com.cerr.hibernate.n2n.Category" column="c_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
测试类的方法修改为如下:
@org.junit.Test
public void test(){
Category category = new Category();
category.setName("AA");
Category category1 =new Category();
category1.setName("BB");
Item item = new Item();
Item item1 = new Item();
item.setName("aa");
item1.setName("bb");
//设定关联关系
category.getItems().add(item);
category.getItems().add(item1);
category1.getItems().add(item);
category1.getItems().add(item1);
item.getCategories().add(category);
item.getCategories().add(category1);
item1.getCategories().add(category);
item1.getCategories().add(category1);
//执行保存操作
session.save(category);
session.save(category1);
session.save(item);
session.save(item1);
}
映射继承关系
对于面向对象的程序设计语言而言,继承和多态是两个最基本的概念,Hibernate的继承映射可以理解持久化类之间的继承关系。例如:人和学生之间的关系,学生继承了人,可以认为学生是一个特殊的人,如果对人进行查询,学生的实例也将被得到。
采用subclass元素的继承映射
- 采用
subclass
的继承映射可以实现对于继承关系中父类和子类使用同一张表 - 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到底是哪个类的实例,这个列被称为辨别者列。
- 在这种映射策略下,使用
subclass
来映射子类,使用class
或subclass
的discriminator-value
属性指定辨别者列的值 - 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中。
新建实体类Person
和Student
类,Student
类继承自Person
类。
package com.cerr.hibernate.subclass;
public class Person {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.cerr.hibernate.subclass;
public class Student extends Person{
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
映射文件Person.hbm.xml
:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.cerr.hibernate.subclass">
<!--加入select-before-update="true" -->
<class name="Person" table="persons" schema="hibernate5" discriminator-value="Person">
<id name="id" column="id">
<generator class="native" />
</id>
<!-- 配置辨别者列-->
<discriminator column="type" type="java.lang.String"></discriminator>
<property name="name" column="name"/>
<property name="age" column="age"/>
<!-- 映射子类Student,使用subclass进行映射-->
<subclass name="Student" discriminator-value="Student">
<property name="school" type="java.lang.String" column="school"></property>
</subclass>
</class>
</hibernate-mapping>
在Hibernate配置文件中添加:
<mapping resource="com/cerr/hibernate/subclass/Person.hbm.xml"/>
。
测试类:
package com.cerr.hibernate.subclass;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import java.util.List;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
/**
* 缺点:
* 1.使用了辨别者列
* 2.子类独有的字段不能添加非空约束
* 3.若继承层次很深,则数据表的字段也会较多。
*/
/**
* 查询
* 1.查询父类记录,只需要查询一张数据表
* 2.对于子类记录,也只需要查询一张数据表
*/
@org.junit.Test
public void testQuery(){
List<Person> personList = session.createQuery("FROM Person ").list();
System.out.println(personList.size());
List<Student> students = session.createQuery("FROM Student").list();
System.out.println(students.size());
}
/**
* 插入操作:
* 1.对于子类对象只需把记录插入到一张数据表中
* 2.辨别者列由Hibernate自动维护。
*/
@org.junit.Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("aa");
session.save(person);
Student student = new Student();
student.setAge(12);
student.setSchool("aa");
student.setName("aaa");
session.save(student);
}
}
运行后新建了一个表,如下:
采用joined-subclass元素的继承映射
采用
joined-subclass
元素的继承映射可以实现每个子类一张表采用这种映射策略时,父类示例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性则保存在子类表中。
在这种映射策略下,无需使用鉴别者列。但需要为每个子类使用key元素映射共有主键。
子类增加的属性可以添加非空约束,因为子类的属性和父类的属性没有保存在同一个表中。
我们还是用上述的Person
和Student
的例子,我们需要在Person.hbm.xml
文件中进行修改:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.cerr.hibernate.joined">
<class name="Person" table="persons" >
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" column="name"/>
<property name="age" column="age"/>
<!-- 使用joined-subclass -->
<joined-subclass name="Student" table="students">
<key column="student_id"></key>
<property name="school" type="java.lang.String" column="school"></property>
</joined-subclass>
</class>
</hibernate-mapping>
然后在配置文件中加上:
<mapping resource="com/cerr/hibernate/joined/Person.hbm.xml"/>
测试类还是使用上面的,运行后将会生成两张表,一张父类表,一张子类独有的属性的表,子类的数据为父类表的字段+子类表的字段。
对于插入操作,插入子类对象需要插两张表。
对于查询操作,查询父类记录时,做一个左外连接查询;查询子类记录,做一个内连接查询。
采用union-subclass元素的继承映射
- 采用
union-subclass
元素可以实现将每一个实体对象映射到一个独立的表中。 - 子类增加的属性可以有非空约束,即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。
- 子类实例的数据仅保存在子类表中,而在父类表中没有任何记录。
- 在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段加上子类独有属性的总和。
- 在这种映射策略下,既不需要使用鉴别者列,也无序使用
key
元素来映射共有主键。 -
使用
union-subclass
映射策略是不可使用identity
的主键生成策略,因为同一类继承层次中所有实体类都需要使用同一个主键种子,即多个持久化实体对应的记录的主键应该是连续的,受此影响,也不该使用native
主键生成策略,因为native
会根据数据库来选择使用identity
或sequence
。
新建Person
类和Student
类
Person
类:
package com.cerr.hibernate.union.subclass;
public class Person {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Student
类:
package com.cerr.hibernate.union.subclass;
public class Student extends Person {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
Person.hbm.xml
文件:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.cerr.hibernate.union.subclass">
<class name="Person" table="PERSONS" >
<id name="id" column="ID" type="java.lang.Integer">
<generator class="increment" />
</id>
<property name="name" column="NAME"/>
<property name="age" column="AGE"/>
<union-subclass name="Student" table="STUDENTS">
<property name="school" column="SCHOOL" type="java.lang.String"/>
</union-subclass>
</class>
</hibernate-mapping>
测试类:
package com.cerr.hibernate.union.subclass;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
public class Test {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
/**
* 优点:
* 1.不需要使用辨别者列
* 2.子类独有的字段能添加非空约束
*
* 缺点:
* 1.子表中存在冗余的字段
* 2.若更新父表的字段,则更新的效率较低。
*/
/**
* 查询
* 1.查询父类记录,需把父表和子表记录汇总到一起再做查询,性能稍差。
* 2.对于子类记录,也只需要查询一张数据表
*/
@org.junit.Test
public void testQuery(){
List<Person> personList = session.createQuery("FROM Person ").list();
System.out.println(personList.size());
List<Student> students = session.createQuery("FROM Student").list();
System.out.println(students.size());
}
/**
* 插入操作:
* 1.对于子类对象只需把记录插入到一张数据表中。
*/
@org.junit.Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("aa");
session.save(person);
Student student = new Student();
student.setAge(12);
student.setSchool("aa");
student.setName("aaa");
session.save(student);
}
}
生成的表如下: