我是靠谱客的博主 强健眼睛,最近开发中收集的这篇文章主要介绍工作积累——JPA事务中数据更新后查询结果为旧数据的问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

工作积累——JPA事务中数据更新后查询结果为旧数据的问题

最近项目中遇见一个BUG。用户在完成业务后,提示任务完成数据库中也保存正确数据,但是在另一个接口中完成此逻辑后获取到的却是错误的结果。经过排查后发现是在业务完成更新后,单独使用Update方法对数据进行最终调整,然后在后续方法通过ID查询的时候没有获取到最终数据。

问题复现

在最开始完成数据保存后,在后续逻辑中调整中间某些数据的值。现在我们两种可以实现的方式。一种使用Modifying进行更新,一种是直接更新实体。

更新Modifying


@Transactional
public User updateAndQuery(User user) {
log.info("更新操作1 start");
User save = userRepository.save(user);
Long id = save.getId();
String newName = "change" + save.getName();
userRepository.updateUserName(newName,id);
Optional<User> userOptional = userRepository.findById(id);
log.info("更新操作1 end");
return userOptional.orElse(null);
}

更新实体

@Transactional
public User updateAndQuery2(User user) {
log.info("更新操作2 start");
User save = userRepository.save(user);
Long id = save.getId();
String newName = "change" + save.getName();
User userDb = userRepository.findById(id).get();
userDb.setName(newName);
userRepository.save(userDb);
Optional<User> userOptional = userRepository.findById(id);
log.info("更新操作2 end");
return userOptional.orElse(null);
}

测试结果

使用下面的测试代码可以获取下面的日志内容


@Test
public void addUser(){
User user = new User();
user.setName("用户1");
User user2 = new User();
user2.setName("用户2");
user = userService.updateAndQuery(user);
user2 = userService.updateAndQuery2(user2);
log.info("user 更新结果:{}",JSON.toJSONString(user));
log.info("user2 更新结果:{}",JSON.toJSONString(user2));
}

日志结果

2022-11-24 22:08:55.143
INFO 24368 --- [
main] dai.samples.jpa2.service.UserService
: 更新操作1 start
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into test_user (age, name, point, id) values (?, ?, ?, ?)
Hibernate: update test_user set name=? where id in (?)
2022-11-24 22:08:55.210
INFO 24368 --- [
main] dai.samples.jpa2.service.UserService
: 更新操作1 end
2022-11-24 22:08:55.212
INFO 24368 --- [
main] dai.samples.jpa2.service.UserService
: 更新操作2 start
Hibernate: select nextval ('hibernate_sequence')
2022-11-24 22:08:55.216
INFO 24368 --- [
main] dai.samples.jpa2.service.UserService
: 更新操作2 end
Hibernate: insert into test_user (age, name, point, id) values (?, ?, ?, ?)
Hibernate: update test_user set age=?, name=?, point=? where id=?
2022-11-24 22:08:55.294
INFO 24368 --- [
main] dai.UserServiceTest
: user 更新结果:{"id":11,"name":"用户1"}
2022-11-24 22:08:55.294
INFO 24368 --- [
main] dai.UserServiceTest
: user2 更新结果:{"id":12,"name":"change用户2"}

看起来像是更新操作1更新失败了,但是最终在数据库中,操作1最终的结果却是正确的。就是复现了为什么在数据库中结果是正确的,但是其他调用方显示结果却是错误的情况。

原因

可以看到日志中更新操作1 end执行前并没有查询语句,此时获取的是JPA的缓存数据。在Modifying处理之后,JPA缓存中的数据和实际中的数据是不一致的。这就导致获取缓存的数据实际上是Modifying之前的数据。

解决方案

要简单的解决此问题可以在@Modifying注解后面添加clearAutomatically = true。此时在进行更新数据则会正常

@Modifying(clearAutomatically = true)
@Query("update ******")
void updateUserName(String newName, Long id);

根据属性说明了解到此参数作用是执行修改查询后是否应该清除底层持久化上下文

/**
* Defines whether we should clear the underlying persistence context after executing the modifying query.
*
* @return
*/
boolean clearAutomatically() default false;

clearAutomatically 的影响

我们可以在代码中继续追踪参数的去向,可以在ModifyingExecution发现此参数的使用

@Override
protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
if (flush) {
em.flush();
}
int result = query.createQuery(accessor).executeUpdate();
if (clear) {
em.clear();
}
return result;
}

查看em.clear()的说明,其专门提到一句

Changes made to entities that have not been flushed to the database will not be persisted.

对尚未刷新到数据库的实体所做的更改将不会持久化。

在使用JPA的时候,如果我们直接对实体进行编辑的时候,并不需要显式的去调用更新的方法,比如下面的逻辑。

@Transactional
public User updateAndQuery2(User user) {
log.info("更新操作2 start");
User save = userRepository.save(user);
Long id = save.getId();
save.setName("change2" + save.getName());
Optional<User> userOptional = userRepository.findById(id);
log.info("更新操作2 end");
return userOptional.orElse(null);
}

我们只需直接对返回的实体进行编辑,这些后续的修改最终能更新到数据库中。但是如果我们使用下面@Modifying(clearAutomatically = true)的操作,会导致JPA中托管的实体分离,后续对实体的修改将不能最终被自动保存,此时需要主动的调用保存方法来保存数据。

 @Transactional
public User updateAndQuery(User user) {
log.info("更新操作1 start");
User save = userRepository.save(user);
Long id = save.getId();
String newName = "change" + save.getName();
userRepository.updateUserName(newName,id);
save.setName("change2" + save.getName());
Optional<User> userOptional = userRepository.findById(id);
log.info("更新操作1 end");
return userOptional.orElse(null);
}

最后

以上就是强健眼睛为你收集整理的工作积累——JPA事务中数据更新后查询结果为旧数据的问题的全部内容,希望文章能够帮你解决工作积累——JPA事务中数据更新后查询结果为旧数据的问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(34)

评论列表共有 0 条评论

立即
投稿
返回
顶部