目录
- 概述
- 一、问题复现
- 二、问题解析
- 三、解决方案
概述
最近发现个问题,使用jpa对实体进行操作时,即使未调用保存或更新方法,对于实体的相关设值也会自动更新到数据库中
一、问题复现
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {
@Autowired
private UserInfoRepository userInfoRepository;
@Test
public void testUpdateEntity(){
UserInfo userInfo = userInfoRepository.get(2103031645422940000L);
userInfo.setFullName("我不是管理员");
}
}
数据库原始数据如下:
为testUpdateEntity()添加事务注解,此时会自动将实体的相关变更持久化掉数据库中
@Test
@Transactional //事务注解,添加之后jpa会自动把实体的相关变更持久化到数据库中
@Commit // 此注解是为了在单元测试中不让事务进行回滚,从而可以在数据库中看到数据的明显变化
public void testUpdateEntity(){
UserInfo userInfo = userInfoRepository.get(2103031645422940000L);
userInfo.setFullName("我不是管理员");
}
执行testUpdateEntity()方法之后,输出日志如下
数据库数据变更如下
二、问题解析
经过查找资料得知,Hibernate实体对象的生命周期分为三种
- 瞬时状态(Transient)
通过new创建对象后,对象并没有立刻持久化,它并未与数据库中的数据有任何关联,此时Java对象的状态为瞬时状态。Session对于瞬时状态的Java对象是一无所知的,当对象不再被其他对象引用时,它的所有数据也就丢失了,对象将会被Java虚拟机按照垃圾回收机制处理。- 持久状态(Persistent)
当对象与Session关联,被Session管理时,它就处于持久状态。处于持久状态的对象拥有数据库标识(数据库中的主键值)。那么,对象是什么时候与Session发生关联的呢?有两种方法:
第一种,通过Sesison的查询接口,或者get()方法,或者load()方法从数据库中加载对象的时候,加载的对象是与数据库表中的一条记录关联的,此时对象与加载它的Session发生关联;
第二种,瞬时状态的对象,通过Session的save()方法或SaveOrUpdate()方法时,Java对象也与Session发生关联。
对于处于持久状态的对象,Session会持续跟踪和管理它们,如果对象的内部状态发生了任何变更,Hibernate会选择合适的时机(如事务提交时)将变更固化到数据库中。- 游离状态
处于持久状态的对象,脱离与其关联的Session的管理后,对象就处于游离状态。
处于游离状态的对象,Session无法保证对象所包含的数据与数据库中的记录一直,因为Hibernate已经无法感知对该对象的任何操作。
Session提供了两个方法(update()、merge()),将处于游离状态的对象,与一个新的Session发生关联。
此时,对象的状态就从游离状态重新转换为持久状态。
根据Hibernate实体生命周期可知,testUpdateEntity()中的userInfo处于持久化状态,一旦事务提交时,Hibernate会自动将userInfo的相关变更持久化到数据库中。
三、解决方案
既然知道Hibernate在事务提交时会自动将持久化状态的实体的变更持久化到数据库中,那么只要我们让实体脱离持久化状态即可避免这个问题。使用detach()使其脱离持久化状态,如下所示
@Test
@Transactional
@Commit
public void testUpdateEntity(){
UserInfo userInfo = userInfoRepository.get(2103031645422940000L);
userInfo.setFullName("我不是管理员111");
userInfoRepository.detach(userInfo);
}
执行结果如下:
数据库结果:
未对数据库数据造成影响,成功解决问题。