单向关联和双向关联
单双向的通俗理解:
1)单向关联:是指在实体的一端进行关联关系的维护(通常是多端),具有关联关系的实体对象间的加载与访问关系是单向的,即只有一个实体对象可以加载和访问对方,但对方是看不到另一方的。对被动方的操作不会影响到主动方,而主动方的操作会影响到被动方。
2)双向关联:只是在多方和一方同时维护关系,双向关联是指具有关联关系的实体对象间的加载与访问关系是双向的。即,任何一方均可加载和访问另一方,任何一方的操作都会间接影响的另一方。
一般能用单向关联就不用双向关联,原因一是双向关联带来效率问题,还有双向关联使两者之间的维护关系变得复杂,难以维护。
inverse和cascade属性
inverse属性:inverse所描述的是对象之间关联关系的维护方式。维护关系哪两种关系?
1、维护外键的关系,通俗点讲,就是哪一方去设置这个被外键约束的字段的值。就拿上面这个例子来说,staff和dept两张表不管进行什么操作,只要关系到了另一张表,就不可避免的要通过操作外键字段,比如,staff查询自己所属的部门,就得通过被外键约束的字段值到dept中的主键中查找,如果dept想查询自己部门中有哪些员工,就拿着自己的主键值跟staff中的外键字段做比较,找到相同的值则是属于自己部门的员工。 这个是查询操作, 现在如果是添加操作呢,staff表中添加一条记录,并且部门属于dept表中的其中一个,staff中有被外键约束修饰的字段,那是通过staff的insert语句就对这个外键字段赋值,还是让dept对象使用update语句对其赋值呢,两个都能对这个外键字段的值进行操作,谁去操作呢?如果不做设置,两个都会操作,虽然不会出现问题,但是会影响性能,因为staff操作的话,在使用insert语句就能设置外键字段的值了,但是dept也会进行对其进行操作,又使用update语句,这样一来,这个update就显的很多余。
2、维护级联的关系,也就是说如果如果让对方维护关系,则自己方的级联将会失效,对方设置的级联有用,如果自己维护关系,则自己方的级联会有用,但是对方设置的级联就会失效。
注:单向one-to-many关联关系中,不可以设置inverse=“true”,因为被控方的映射文件中没有主控方的信息。
inverse只存在于集合标记的元素中。像many-to-one这一类的标签都不能设置"inverse"这个属性值,它们只能取值"false")[Hibernate提供的集合元素包括
cascade属性
“cascade”-直译过来就是"级联、串联"的意思,书面化的解释为"该属性会使我们在操作主对象时,同时Hibernate帮助我们完成从属对象相应的操作(比如,有Customer和Order这两张表,关系为一对多,只使用JDBC删除Customer表中的一行记录时,我们还需要手动的将Order表中与之关联的记录全都删除,使用Hibernate的’cascade’属性后,当我们删除一条Customer记录时,Hibernate会帮助我们完成相应Order表记录的删除工作,方便了我们的工作)"。
单向关联的应用
这里我们以顾客(Customer)和订单(Order)实现单向一对多的关联关系。
1)实体类
Customer实体包含多个Order,这里以定义一个Order的集合属性,为什么是Set,Set的特性不会有重复元素。
public class Customer {
private Integer id;
private String name;//客户名称
private Set<Order> orders = new HashSet<>(); // 集合初始化,避免NullPonitException
// getter and setter ...
}
public class Order {
private Integer id;
private String orderName;
// getter and setter ...
}
2)配置文件实现关联关系的构造
<?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="cn.entity.Customer" table="customer">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<set name="orders">
<!--key column在多表中生成的外键名称-->
<key column="c_id"/>
<one-to-many class="cn.entity.Order"/>
</set>
</class>
</hibernate-mapping>
<?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 table="t_order" name="cn.entity.Order">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="orderName" column="name"/>
</class>
</hibernate-mapping>
工具类
public class HibernateUtil {
private static SessionFactory sessionFactory;
static {
Configuration configure = new Configuration().configure();
sessionFactory = configure.buildSessionFactory();
// 虚拟机关闭时释放SessionFactory
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
sessionFactory.close();
System.out.println("释放资源");
}
});
}
public static Session getSession() {
return sessionFactory.openSession();
}
}
1-n 单向关联 save操作
观察下面的它和1-n 双向关联有什么差别,善于发现规律!
@Test
// 单向关联 OneToMany 注意点:1)xml配置外键id不要重名,many to one的属性
// inverse所描述的是对象之间关联关系的维护方式。,inverse属性存在于many-to-one many-to-many,
public void testOneToManySave() {
// !!!开启事务
Session session = HibernateUtil.getSession();
session.getTransaction().begin();
Customer customer = new Customer(null, "Stronger3");
Order order1 = new Order();
Order order2 = new Order();
order1.setOrderName("MateBook3");
order2.setOrderName("MagicBook3");
customer.getOrders().add(order1);
customer.getOrders().add(order2);
// 插入顺序没有先后
// 因为order有一个外键关联与主表customer,在customer没有保存之前是否会报错,不会!
// 单向一对多,因为是单向,order表不知道它和customer有关联,hibernate最后会执行二次update来建立关联,
// 最后当参考的主键记录插入完毕后,执行二次更新操作。
session.save(order1);
session.save(order2);
session.save(customer);
// 单向关联,set标签中 设置cascade="save-update"并不会级联保存,因为单向
// Order并不知道它和Customer有关联,所以无效
// session.save(customer);
session.getTransaction().commit();
session.close();
}
执行结果
insert into t_order (name) values (?)
insert into t_order (name) values (?)
insert into customer (name) values (?)
update t_order set c_id=? where id=?
update t_order set c_id=? where id=?
1-n 单向关联 查询操作
@Test
public void testOneToManyGet() {
Session session = HibernateUtil.getSession();
Customer customer = (Customer) session.get(Customer.class, 6);
Set<Order> orders = customer.getOrders();
// Hibernate默认采用了懒加载机制,当使用对象的时候采取db查询,若期间session关闭,会抛出异常
// org.hibernate.LazyInitializationException
// session.close();
System.out.println(orders);
session.close();
}
1-n 单向关联 更新操作
@Test
public void testOneToManyUpdate() {
// update操作 开启事务 根据客户-->修改订单信息
Session session = HibernateUtil.getSession();
session.getTransaction().begin();
Customer customer = (Customer) session.get(Customer.class, 6);
Set<Order> orders = customer.getOrders();
orders.iterator().next().setOrderName("OnePlus");
session.getTransaction().commit();
session.close();
}
1-n 单向关联 删除操作
@Test
public void testOneToManyDel() {
// 删除操作 开启事务 根据客户-->修改订单信息
/**
* 1) hibernate 那么可以直接删除主表,从表相应的外键会置为NULL
* 3) cascade="delete" 设置级联删除,删除主表记录的同时从表记录也会删除,忽略外键约束
* 数据库中外键约束删除属性默认是restrict受限的
* 注:单向one-to-many关联关系中,不可以设置inverse="true",因为被控方的映射文件中没有主控方的信息。
*/
Session session = HibernateUtil.getSession();
session.getTransaction().begin();
// 1) hibernate可直接删除 主表记录,从表c_id置为null
//Customer customer = (Customer) session.get(Customer.class, 5);
//session.delete(customer);
// 2) 设置 cascade="delete" 也可以直接删除。
Customer customer = (Customer) session.get(Customer.class, 3);
session.delete(customer);
session.getTransaction().commit();
session.close();
}
双向关联的应用
实现双向1-n关联
1) 映射文件
<?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="cn.entity.Customer" table="t_customer1">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<!--inverse="true" cascade="delete"-->
<set name="orders">
<key column="c_id"/>
<one-to-many class="cn.entity.Order"/>
</set>
</class>
</hibernate-mapping>
<?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 table="t_order1" name="cn.entity.Order">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="orderName" column="name"/>
<many-to-one name="customer" column="c_id" cascade="save-update"/>
</class>
</hibernate-mapping>
2)实体类,Customer实体类不变,Order实体类新增Customer实体属性,主要是为了建立双向1-n的关联。
3)测试类
下面代码结合各个实体的映射文件,通过在.hbm.xml文件中设置相应的inverse和cascade属性完成相应的测试,可以发现,双向关联,结合inverse和cascade可以玩出各种各样的操作,比较复杂,这也就是为什么推荐使用单向关联。
cascade作用在哪个实体(A)上,对这个实体(A)进行的CRUD,有关联的实体(B)也会间接收到影响,而不是作用在B上对A操作,B受到关联。 搞清楚他们之间的关系。
@Test
public void testOneToManySave() {
Session session = HibernateUtil.getSession();
session.getTransaction().begin();
Customer customer = new Customer(null, "test");
Order order1 = new Order();
Order order2 = new Order();
order1.setOrderName("Nova");
order2.setOrderName("Intel");
customer.getOrders().add(order1);
customer.getOrders().add(order2);
// 建立双向关联,一端交予控制权给多端后,关联关系由多端控制,
// 如果没有设置关联(order1.setCustomer(customer);没有设置),
// 但是一端交予了控制权给多端,那么外键一列可能为空,因为少了update
order1.setCustomer(customer);
order2.setCustomer(customer);
/*1. 双向关联且inverse默认情况下,执行3次insert+2次update,建议先插入一端,在插入多端,它和单向的执行语句次数一样
但是在插入order的时候附带了外键字段,在单向是没有的,只有name属性,原因就是双向,前提order.setCustomer
之后,才建立了双向关联,否则你无论是否多端维护关联关系,order表中c_id一列总是为空,order知道
和Customer有外键关联,为什么还会执行update呢?因为双方都会维护关联关系,hibernate为了万无一失,万一你没有
order.setCustomer设置外键关联,多执行的update就有效了。
会多执行update语句。
insert into t_customer1 (name) values (?)
insert into t_order1 (name, c_id) values (?, ?)
insert into t_order1 (name, c_id) values (?, ?)
update t_order1 set c_id=? where id=?
update t_order1 set c_id=? where id=?*/
/*2. 若插入有外键关联的数据很多,这样无疑带来效率问题,所以说,为了避免update操作,我们可以把控制权
交给多的一方,通过在 <set name="orders" inverse="false">
执行结果 少了2次update
insert into t_customer1 (name) values (?)
insert into t_order1 (name, c_id) values (?, ?)
insert into t_order1 (name, c_id) values (?, ?)*/
/*session.save(customer);
session.save(order1);
session.save(order2);*/
// 1) 一端设置cascade="save-update" inverse=true,说明多端维护关系,并不会级联保存,
// 即session.save(customer);保存customer实体的时候,order表中不会增加记录
// 2) 若两端都维护关联,即默认状态inverse,一端设置cascade="save-update"
// 即session.save(customer);保存customer实体的同时,order也会添加相应记录
// 该语句就相当保存了customer实体,对order不会有任何影响
// session.save(customer);
//双向关联 多端设置cascade="save-update",若多端控制,对多端保存记录,一端也会级联保存
// 不需要在customer了
session.save(customer);
session.save(order1);
session.save(order2);
session.getTransaction().commit();
session.close();
}
@Test
// 级联的设置位置不同影响也不同
public void testManyToOneDelete() {
// 恢复初始关联控制权
Session session = HibernateUtil.getSession();
session.getTransaction().begin();
// 不设置级联,对customer进行删除,将customer记录删除后,相应订单c_id的属性变为null
//执行语句
// select customer0_.id as id2_0_, customer0_
// update t_order1 set c_id=null where c_id=?
// delete from t_customer1 where id=?
// Customer customer = (Customer) session.get(Customer.class, 14);
// session.delete(customer);
//执行语句
// 级联删除订单记录,结果相应顾客记录也被删除
// select customer0_.id as id2_0_, custom
// update t_order1 set c_id=null where c_
// delete from t_order1 where id=?
// delete from t_customer1 where id=?
Object o = session.get(Order.class, 30);
session.delete(o);
session.getTransaction().commit();
session.close();
}
保存和删除不在列举了,主要是弄懂单向双向关系,inverse和cascade,一切就好说了。
多对多关联(双向)
一个学生可以选择多门课程,反之同理,这里以学生和课程为例,话不多说,上代码
Course实体和Student实体
/**
* @author WuChangJian
* @date 2020/4/16 9:46
*/
public class Student {
private int sid;
private String name;
private Set<Course> courses = new HashSet<>();
// getter and setter ...
}
public class Course {
private int cid;
private String name;
private Set<Student> students = new HashSet<>();
// getter and setter ...
}
映射文件,代码注释属性给与了相应的说明
<?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="cn.entity.Student" table="t_stu">
<id name="sid" column="sid">
<!--指明主键生成策略-->
<generator class="native"/>
</id>
<property name="name" column="name"/>
<!--指定中间表-->
<set name="courses" table="t_stu_course" cascade="all-delete-orphan">
<key column="sid"/>
<many-to-many column="cid" class="cn.entity.Course"/>
</set>
</class>
</hibernate-mapping>
<?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="cn.entity.Course" table="t_course">
<id name="cid" column="cid">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<!--set
name:对方的集合的属性名称
table:多对多使用的中间表的名称-->
<set name="students" table="t_stu_course" inverse="true">
<!--key
column:当前的对象对应中间表的外键名称-->
<key column="cid"/>
<!--many-to-many
class :对方类的全路径
column:对方的对象在中间表的外键名称-->
<many-to-many class="cn.entity.Student" column="sid"/>
</set>
</class>
</hibernate-mapping>
测试结合属性标签添加相应属性测试,比如级联保存,inverse属性放弃维护权。什么叫建立关联,我的理解是两方实体在属性上都设置上了各自的引用。比如通过add方法建立了关联。
Session session = HibernateUtil.getSession();
session.beginTransaction();
Student s1 = new Student("xiaoming");
Student s2 = new Student("xiaohong");
Course c1 = new Course( "Math");
Course c2 = new Course("Chinese");
s1.getCourses().add(c1);
s1.getCourses().add(c2);
s2.getCourses().add(c2);
c1.getStudents().add(s1);
c2.getStudents().add(s1);
c2.getStudents().add(s2);
/**
* 下面执行会报错
* Caused by: java.sql.BatchUpdateException: Duplicate entry '2-2' for key 'PRIMARY'
* 因为默认情况下两个表都会维护关系(inverse=false),导致重复向中间表插入数据,
* 解决方法:一方放弃维护,一般我们把维护权交给操作多的实体类,这里我们使Course放弃维护,
* 1)修改映射文件放弃维护:inverse=true
* 2)注释掉建立关联的代码
* c1.getStudents().add(s1);
* c2.getStudents().add(s1);
* c2.getStudents().add(s2);
*/
session.save(s1);
session.save(s2);
/**
* 若在Student中设置级联保存,下面代码可以省略
*/
session.save(c1);
session.save(c2);
session.getTransaction().commit();
session.close();
完结!你的点赞是我创作的动力!👍