spring data jpa cascade级联操作研究

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

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

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

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

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

先说一下测试用到的表

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

两张表的主键都为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信息也级联保存了。

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

关键测试代码如下:

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的表现。

测试前数据库状态

测试关键代码

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

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

 cascade数据库设置了外键约束数据库没有设置外键约束
CascadeType.ALL;REMOVEREMOVE
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信息进行更新即可。

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

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

首先我们会在数据库中查询一次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;
CascadeType.MERGE;同DETACH
 
CascadeType.PERSIST;同DETACH
CascadeType.REFRESH;
 
CascadeType.REMOVE;同DETACH

 

5. CascadeType.DETACH

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

 

总结

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

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

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring Data JPA 是什么? Spring Data JPASpring 框架中的一个模块,它提供了一种方便的方式来访问和操作数据库,同时也简化了开发人员的工作。它基于 JPA 规范,提供了一些常用的 CRUD 操作方法,同时也支持自定义查询和分页查询等功能。 Spring Data JPA 的优点是什么? Spring Data JPA 的优点包括: 1. 简化了数据访问层的开发,提高了开发效率。 2. 提供了一些常用的 CRUD 操作方法,减少了重复的代码编写。 3. 支持自定义查询和分页查询等功能,提高了查询效率。 4. 可以与 Spring 框架无缝集成,方便使用。 5. 支持多种数据库,包括关系型数据库和 NoSQL 数据库等。 6. 提供了一些高级特性,如二级缓存、延迟加载等。 Spring Data JPA 的缺点是什么? Spring Data JPA 的缺点包括: 1. 学习曲线较陡峭,需要掌握 JPA 规范和 Spring 框架的相关知识。 2. 对于复杂的查询,需要编写自定义查询语句,增加了开发难度。 3. 对于大规模数据的查询和操作,可能会出现性能问题。 4. 对于一些特殊的需求,可能需要使用原生 SQL 或其他 ORM 框架来实现。 Spring Data JPAHibernate 有什么区别? Spring Data JPA 是基于 JPA 规范的,而 Hibernate 是一个 ORM 框架,它实现了 JPA 规范。因此,Spring Data JPAHibernate 之间的区别主要在以下几个方面: 1. Spring Data JPA 是一个数据访问层框架,而 Hibernate 是一个 ORM 框架。 2. Spring Data JPA 提供了一些常用的 CRUD 操作方法,而 Hibernate 更加灵活,可以编写任意复杂的查询语句。 3. Spring Data JPA 可以与 Spring 框架无缝集成,而 Hibernate 可以与任何 Java 应用程序集成。 4. Spring Data JPA 支持多种数据库,包括关系型数据库和 NoSQL 数据库等,而 Hibernate 主要支持关系型数据库。 5. Spring Data JPA 提供了一些高级特性,如二级缓存、延迟加载等,而 Hibernate 也提供了类似的特性。 如何使用 Spring Data JPA? 使用 Spring Data JPA 的步骤如下: 1. 添加依赖:在项目的 pom.xml 文件中添加 Spring Data JPA 的依赖。 2. 配置数据源:在 Spring 的配置文件中配置数据源。 3. 定义实体类:定义与数据库表对应的实体类,并使用 JPA 注解进行映射。 4. 定义 DAO 接口:定义一个继承 JpaRepository 接口的 DAO 接口。 5. 编写业务逻辑:在 Service 层中编写业务逻辑,调用 DAO 接口中的方法进行数据操作。 6. 运行程序:启动应用程序,测试数据访问和操作是否正常。 如何进行分页查询? 使用 Spring Data JPA 进行分页查询的步骤如下: 1. 在 DAO 接口中定义一个继承 PagingAndSortingRepository 接口的方法。 2. 在 Service 层中调用 DAO 接口中的分页查询方法,并指定分页参数。 3. 在控制器中接收分页参数,并将查询结果传递给前端页面。 4. 在前端页面中显示分页信息和查询结果。 如何进行自定义查询? 使用 Spring Data JPA 进行自定义查询的步骤如下: 1. 在 DAO 接口中定义一个自定义查询方法,并使用 @Query 注解指定查询语句。 2. 在 Service 层中调用 DAO 接口中的自定义查询方法。 3. 在控制器中接收查询结果,并将结果传递给前端页面。 4. 在前端页面中显示查询结果。 如何进行事务管理? 使用 Spring Data JPA 进行事务管理的步骤如下: 1. 在 Spring 的配置文件中配置事务管理器。 2. 在 Service 层中使用 @Transactional 注解标记需要进行事务管理的方法。 3. 在控制器中调用 Service 层中的方法。 4. 如果方法执行成功,则事务会自动提交,否则事务会自动回滚。 如何进行多表查询? 使用 Spring Data JPA 进行多表查询的步骤如下: 1. 在 DAO 接口中定义一个自定义查询方法,并使用 @Query 注解指定查询语句。 2. 在查询语句中使用 JOIN 关键字连接多个表。 3. 在 Service 层中调用 DAO 接口中的自定义查询方法。 4. 在控制器中接收查询结果,并将结果传递给前端页面。 5. 在前端页面中显示查询结果。 如何进行级联操作? 使用 Spring Data JPA 进行级联操作的步骤如下: 1. 在实体类中使用 @OneToMany 或 @ManyToOne 注解定义关联关系。 2. 在 Service 层中编写业务逻辑,调用 DAO 接口中的方法进行级联操作。 3. 在控制器中接收操作结果,并将结果传递给前端页面。 4. 在前端页面中显示操作结果。 如何进行缓存管理? 使用 Spring Data JPA 进行缓存管理的步骤如下: 1. 在 Spring 的配置文件中配置缓存管理器。 2. 在实体类中使用 @Cacheable 或 @CacheEvict 注解指定缓存策略。 3. 在 Service 层中编写业务逻辑,调用 DAO 接口中的方法进行数据操作。 4. 在控制器中接收操作结果,并将结果传递给前端页面。 5. 在前端页面中显示操作结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值