一、hibernate 关联关系映射 (多表映射配置 和 数据 增加、删除 )
1、 系统模型中 实体设计三种关系
关系型数据库 设计 通常用E-R 绘制
概念E-R图也称实体-联系图(EntityRelationshipDiagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。
不同实体关系 建表原则
一对多: 在多个一方添加 一的一方 主键作为外键
多对多: 产生中间关系表,引入两个实体主键,作为外键 ,两个主键成为联合主键
一对一: 在任意一方 引入对方主键 作为外键 (开发中使用非常少 )
二、一对多关联关系映射
1、 一对多关联映射的配置和 Java对象编写
客户类:
package lsq.hibernate.onetomany;
import java.util.HashSet;
import java.util.Set;
/**
* 客户类
*
* @author Daniel Li
*
*/
public class Customer {
private Integer id;
private String name;
// 一个客户对应多个订单
private Set<Order> orders = new HashSet<Order>();
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<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
}
订单类:
package lsq.hibernate.onetomany;
/**
* 订单类
*
* @author Daniel Li
*
*/
public class Order {
private Integer id;
private Double money;
private String receiverinfo;
// 一个订单关联一个客户
private Customer customer;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getReceiverinfo() {
return receiverinfo;
}
public void setReceiverinfo(String receiverinfo) {
this.receiverinfo = receiverinfo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
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>
<class name="lsq.hibernate.onetomany.Order" table="orders" catalog="hibernate3day2">
<id name="id">
<generator class="native"></generator>
</id>
<property name="money"></property>
<property name="receiverinfo"></property>
<!-- 一个订单关联一个客户 -->
<!--
name:订单类中客户属性名
class:关联属性的类型
column:列名,关联属性添加外键约束
-->
<many-to-one name="customer" class="lsq.hibernate.onetomany.Customer" column="customer_id" cascade="save-update" not-null="true"></many-to-one>
</class>
</hibernate-mapping>
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>
<class name="lsq.hibernate.onetomany.Customer" table="customers" catalog="hibernate3day2">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<!-- 一个客户关联多个订单 -->
<!-- name:关联订单集合属性名 -->
<set name="orders" cascade="save-update" inverse="true">
<!-- 客户关联订单后,在对方表中添加外键列 -->
<!-- column:对方表外键列列名 -->
<key column="customer_id"></key>
<!-- 一对多配置 -->
<!-- class是orders集合元素类型 -->
<one-to-many class="lsq.hibernate.onetomany.Order"/>
</set>
</class>
</hibernate-mapping>
案例一: 一对多保存操作
package lsq.hibernate.onetomany;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import lsq.hibernate.utils.HibernateUtils;
/**
* 编写一对多测试用例
*
* @author Daniel Li
*
*/
public class HibernateTest {
// 插入数据
@Test
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("Curry");
Order order1 = new Order();
order1.setMoney(100d);
order1.setReceiverinfo("美国 奥克兰");
Order order2 = new Order();
order2.setMoney(1000d);
order2.setReceiverinfo("中国 上海");
session.save(customer);
session.save(order1);
session.save(order2);
// 建立客户和订单的联系
customer.getOrders().add(order1);
customer.getOrders().add(order2);
order1.setCustomer(customer);
order2.setCustomer(customer);
transaction.commit();
session.close();
}
}
案例二 : 保存操作,只保存客户或者只保存订单是否可以
默认情况下 异常
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: cn.itcast.onetomany.Order
原因: 持久化对象关联瞬时态对象。
// 保存订单同时保存客户
// 订单级联客户,订单--->客户,在Order.hbm.xml中添加
// <many-to-one name="customer" class="lsq.hibernate.onetomany.Customer"
// column="customer_id" cascade="save-update" ></many-to-one>
@Test
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("Durant");
Order order1 = new Order();
order1.setMoney(100d);
order1.setReceiverinfo("美国 华盛顿");
session.save(order1);
// 建立关系
order1.setCustomer(customer);
transaction.commit();
session.close();
}
在添加用户时,同时将用户关联订单也保存到数据表 (将订单自动保存 )
级联保存 : 当一个对象是持久化对象,该对象关联瞬时/脱管 对象,持久态对象 自动对关联对象 进行保存或者更新操作
* 保存客户 同时 保存关联订单
在Customer.hbm.xml 配置 <set name="orders" cascade="save-update">
* 保存订单 同时 保存关联客户
在Order.hbm.xml 配置 <many-to-one name="customer" cascade="save-update"></many-to-one>
练习: 对象导航练习
//练习
@Test
public void demo4() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("Green");
Order order1 = new Order();
order1.setMoney(100d);
order1.setReceiverinfo("美国 奥克兰");
Order order2 = new Order();
order2.setMoney(1000d);
order2.setReceiverinfo("中国 上海");
Order order3 = new Order();
order3.setMoney(10000d);
order3.setReceiverinfo("中国 北京");
// 建立客户和订单的联系
order1.setCustomer(customer);
customer.getOrders().add(order2);
customer.getOrders().add(order3);
// 两边都配置cascade="save-update"
session.save(order1);// 插入几条数据?4
// session.save(customer);//插入几条数据?3
// session.save(order2);//插入几条数据?1
transaction.commit();
session.close();
}
案例三 : 级联删除
删除订单时,关联客户是否 也被删除 ??? 删除客户时,订单会不会删除 ???
* 默认情况下 ,删除客户时,将相关订单外键设置为null , 完成删除
* 如果设置 Order.hbm.xml 中 <manytoone name="customer" not-null="true" /> 必须 设置inverse=true
不允许存在 没有客户的订单 !!!! 在开发时,需要删除客户时, 级联删除该客户的订单
* 配置 Customer.hbm.xml cascade="delete"
* 删除脱管对象无法产生级联删除效果, 必须删除持久对象
@Test
// 删除客户, 记录删除订单
// 客户 ---> 订单 , 配置Customer.hbm.xml
// <set name="orders" cascade="delete">
public void demo5() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 删除脱管 (无法产生级联效果)
// Customer customer = new Customer();
// customer.setId(1);
// 删除持久对象
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
transaction.commit();
session.close();
}
案例四、孤儿删除 (孤子删除 )
在一对多模型中,存在父子表关系。例如:客户和订单,一方通常是父方,多方是子方,不存在没有客户的订单。
问题:解除客户和订单关系时,订单是否有效?---无效
对于无效订单,应该删除,客户解除和订单关系时,自动删除订单。
孤儿删除步骤:
1) 在Customer.hbm.xml 配置 <set name="orders" cascade="delete-orphan">
2) 在程序 customer.getOrders().remove(order); 删除解除关系的订单对象
@Test
// 孤儿删除
// 客户 ---> 订单 , 配置Customer.hbm.xml
// <set name="orders" cascade="delete-orphan">
public void demo6() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//解除1号订单和1号客户的关系
Customer customer = (Customer) session.get(Customer.class, 1);
Order order = (Order) session.get(Order.class,1);
customer.getOrders().remove(order);
transaction.commit();
session.close();
}
☆:cascade属性取值, 有JPA规范定义的, Hibernate框架实现JPA规范,对其进行扩展
* save-Update 级联保存更新,持久对象 关联瞬时对象 执行save, 关联脱管对象 执行update
* delete 级联删除
* delete-orphan 主要用于一对多模型,进行孤儿删除
* all 除掉delete-orphan外 所有级联关系
* all-delete-orphan 包含 all和 delete-orphan
案例五、双向维护 --- 多余的SQL
问题:将1号订单改为属于2号客户 ---------- 产生两条SQL语句,怎么解决?
不要在一对多模型中,使双方都具有 外键维护能力
* 设置inverse属性 --- 单向维护
通过inverse属性来设置由双向关联的哪一方来维护表和表之间的关系. inverse=false 的为主动方,inverse=true 的为被动方, 由主动方负责维护关联关系
* 哪一方设置 inverse=true,代表放弃外键维护能力 ,在实际开发中 通常由多方来维护外键 , 在一方添加 inverse=true
//多余SQL语句
//在Customer.hbm.xml中配置inverse=“true"
@Test
public void demo7() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Order order = (Order) session.get(Order.class, 1);
Customer customer = (Customer)session.get(Customer.class, 2);
order.setCustomer(customer);
customer.getOrders().add(order);
transaction.commit();
session.close();
}
cascade 进行级联操作,如果配置cascade 对数据进行级联操作, inverse 属性只是针对外键列维护能力 ,如果设置inverse=true,将外键维护交给另一方
//inverse和cascade的区别
//<set name="orders" cascade="save-update" inverse="true">
@Test
public void demo8(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("Wade");
Order order = new Order();
order.setMoney(1000d);
order.setReceiverinfo("芝加哥");
//建立客户和订单关系
customer.getOrders().add(order);
//保存客户
session.save(customer);
//问题:订单数据会不会被保存?
transaction.commit();
session.close();
}
执行结果:(会级联保存订单,但是不会维护订单中客户ID外键列)
小结:
1) 一对多存在 父子关系,一方是父方,多方是子方, 子方数据 依赖 父方数据
2) 通常将cascade 配置在 父方 (一的一方 客户表)
例如: 添加客户时 添加订单, 删除客户时 删除订单 , 孤儿删除
3) 外键方维护权,通常交给多方负责,需要在一方配置 inverse="true"
<set name="orders" cascade="all-delete-orphan" inverse="true" >