工作积累——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);
    }
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值