欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring data jpa cascade级联操作研究

程序员文章站 2022-03-02 19:00:43
...

由于平时用mybatis比较多,刚接触spring data jpa的时候,对一对多,多对多关联映射以及关联之间的级联操作学的很迷糊,于是自己实验总结了一下spring data jpa中的6种级联操作。

spring data jpa的级联操作有如下6种CascadeType.ALL,CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH,CascadeType.REMOVE。

spring data jpa cascade级联操作研究

其中ALL代表包含所有其他5种,所以我们只需要研究其他5种即可。

对于使用的测试代码,后文只列出了关键代码,想要自己动手尝试的小伙伴可以从以下地址下载代码。

https://gitee.com/zzjzzy/spring-data-jpa-study 

先说一下测试用到的表

数据库有两张表,如下,user表和department表为多对一,一个用户属于一个部门,一个部门可以有多个用户,在用户表中用dept_id维护关联关系。

spring data jpa cascade级联操作研究

spring data jpa cascade级联操作研究

两张表的主键都为int,自增。

表所对应的实体类分别为User和Department,具体代码比较长,就不贴出来了,感兴趣的可以去上面的仓库地址查看。

这里只列出User实体中比较重要的一部分代码如下,User实体中有一个department字段,用来维护关联关系,Department实体就是普通的java bean,没有维护关联关系。

@ManyToOne(targetEntity = Department.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
@JoinColumn(name = "dept_id", referencedColumnName = "id")
private Department department;

 

1. CascadeType.PERSIST

persist是持久化的意思,所以这个级联操作的意思是在我保存user表数据时,如果User实体中有department信息,会把department信息也级联保存了。

在做测试之前,我们的数据库初始状态如下:

spring data jpa cascade级联操作研究

spring data jpa cascade级联操作研究

关键测试代码如下:

Department department = new Department();
department.setCode("YXB");
department.setName("营销部");
User user = new User();
user.setDepartment(department);
user.setName("zzj");
repository.save(user);

测试结果如下:

 cascade 结果
CascadeType.ALL; PERSIST
CascadeType.DETACH; 报错
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.example.demo.User.department -> com.example.demo.Departmen
CascadeType.MERGE; 报错,同上
CascadeType.PERSIST; user表新增一条记录
同时department表新增一条YXB, 营销部的记录
CascadeType.REFRESH; 报错,同上
CascadeType.REMOVE; 报错,同上

可以看到,只有ALL和PERSIST成功保存了User实体中携带的department信息。

 

2. CascadeType.MERGE

merge意思是合并的意思,就是当User实体中的department数据有更新时,保存user时也会更新department信息,注意是更新,如果User实体中持有的是一个数据库中没有的department,是会像上面一样报错的。

测试前数据库初始状态:同上

测试关键代码

Department department = new Department();
department.setId(9);
department.setCode("YXB");
department.setName("营销部");
User user = new User();
user.setDepartment(department);
user.setName("zzj");
repository.save(user);

测试结果:

 cascade 结果
CascadeType.ALL; MERGE
CascadeType.DETACH; user表新增一条dept_id为9的记录
department表数据不变
CascadeType.MERGE; user表新增一条dept_id为9的记录
同时department表数据更新为YXB, 营销部
CascadeType.PERSIST; user表新增一条dept_id为9的记录
department表数据不变
CascadeType.REFRESH; user表新增一条dept_id为9的记录
department表数据不变
CascadeType.REMOVE; user表新增一条dept_id为9的记录
department表数据不变

 

3. CascadeType.REMOVE

remove就是删除的意思,就是在删除user表数据时会关联删除department表数据,但是这就分两种情况,比如我要删除一个user,这个user的dept_id为9,那么程序就会尝试删除department表中id为9的数据,但是如果user表还有其他记录的dept_id也为9,那么把id为9的department记录删除就是有问题的,下面我们来测试一下spring data jpa的表现。

测试前数据库状态

spring data jpa cascade级联操作研究

spring data jpa cascade级联操作研究

测试关键代码

Department department = new Department();
department.setId(9);
User user = new User();
user.setId(1);
user.setDepartment(department);
repository.delete(user);

测试结果

 cascade 结果
CascadeType.ALL; REMOVE
CascadeType.DETACH; user表记录被删除
department表没有
CascadeType.MERGE; user表记录被删除
department表没有
CascadeType.PERSIST; user表记录被删除
department表没有
CascadeType.REFRESH; user表记录被删除
department表没有
CascadeType.REMOVE; user表记录被删除
department表也被删除

接下来我们把user表的初始状态改为如下状态,也就是有两个user的dept_id都为9

spring data jpa cascade级联操作研究

测试结果,测试结果要分如下两种情况,注意区分。

 cascade 数据库设置了外键约束 数据库没有设置外键约束
CascadeType.ALL; REMOVE REMOVE
CascadeType.DETACH; user表记录被删除
department表没有
user表记录被删除
department表没有
CascadeType.MERGE; user表记录被删除
department表没有
user表记录被删除
department表没有
CascadeType.PERSIST; user表记录被删除
department表没有
user表记录被删除
department表没有
CascadeType.REFRESH; user表记录被删除
department表没有
user表记录被删除
department表没有
CascadeType.REMOVE;

报错:java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`user`, CONSTRAINT `FKmf82od1cs7u7drq5eua8ukyrw` FOREIGN KEY (`dept_id`) REFERENCES `department` (`id`))

user表记录被删除
department表也被删除

 

4. CascadeType.REFRESH

refresh是级联刷新的意思,这里要理解一下什么是刷新,java定义的jpa规范中有个EntityManager接口,里面有个EntityManager.refresh(Object entity);的方法,调用这个方法时会从数据库重新读取数据,更新entity实体的数据。用我们的例子来解释就是,当我设置了级联类型为REFRESH时,我对user调用refresh方法时,user中的department字段信息也会从数据库中读取最新数据进行更新。如果没有设置REFRESH级联类型,就只会更新user实体的信息,department字段的信息是不会更新的。

这个要做测试的话会麻烦一些,因为我们要在程序中注入EntityManager的一个实例,同时要对UserRepository开放一个refresh方法,然后调用repository.refresh(user)方法来进行测试。

具体如何在spring中注入一个EntityManager不是本文的重点了,这里贴一个链接,感兴趣的可以研究一下。https://dzone.com/articles/accessing-the-entitymanager-from-spring-data-jpa 

如果不想研究如何注入EntityManager,那么在看后面的代码时你只需要知道,在调用reposity.refresh(user)时,就是要从数据库重新读取最新的数据,对user信息进行更新即可。

同样,先贴出来测试之前数据库初始状态

spring data jpa cascade级联操作研究

spring data jpa cascade级联操作研究

测试关键代码,由于这个复杂一点,所以说一下大概的测试思路。

首先我们会在数据库中查询一次user以及user关联的department信息,然后查询完后让程序停住(可以通过断点调试或Thread.spleep()),然后我们把数据库中user表name字段从“张三”手动修改为“李四”,department表的name字段从“综合部”改为"综合部门",然后放行程序,让程序调用refresh方法对User实体进行刷新,查看刷新后的user以及user中department字段值的变化。

代码中还有很多要注意的地方,具体可以看代码中注释。

//如果我们不开启事务,那么第一次查询完后session就关闭了,再调用EntityManager.refresh()方法会报错,因为session已经关闭了。所以在测试之前要开启事务。
//开启事务后还要调整事务隔离级别为读已提交,因为spring默认事务是可重复读,由于我们是在同一个事务中两次查询,以查看refresh的效果,在可重复读的隔离级别下,即使我们修改了数据库的数据,两次查询的结果也肯定是一样的,也就是我们的修改对当前事务是不可见的,refresh的效果也就看不出来了。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
Optional<User> byId = repository.findById(1);
User user = byId.get();
System.out.println(user);
repository.refresh(user);  //在此处打断点,修改数据库数据后再向下执行
System.out.println(user);
transactionManager.commit(transaction);

测试结果,可以看出,只有在REFRESH开启时,调用refresh方法时才会级联更新department的信息。

 cascade 结果
CascadeType.ALL; REFRESH
CascadeType.DETACH; spring data jpa cascade级联操作研究
CascadeType.MERGE; 同DETACH
 
CascadeType.PERSIST; 同DETACH
CascadeType.REFRESH; spring data jpa cascade级联操作研究
 
CascadeType.REMOVE; 同DETACH

 

5. CascadeType.DETACH

detach的意思就是脱离关系,也是就user的任何操作都不会级联到department,从上面的四个测试也可以看出detach的结果了,所以这里就不再测试了。

 

总结

从以上测试我们可以看出,除了detach,其他4个级联类型其实正好对应了增删改查4个操作。

persist对级联新增有影响,remove对级联删除有影响,merge对级联修改有影响,refresh对级联查询有影响。