Java Spring数据JPA:提交了隔离读取的事务在另一个事务中看不到提交的数据

Java Spring数据JPA:提交了隔离读取的事务在另一个事务中看不到提交的数据,java,spring,hibernate,transactions,isolation-level,Java,Spring,Hibernate,Transactions,Isolation Level,因此,我有一个非常简单的基于Spring Boot的web应用程序 数据库有一个表,user,其中包含列id和username,还有一条记录:(1,'Joe') 我还有以下课程: User-映射到表User UserRepository-一个Spring数据JPA存储库 UserService+DefaultUserService-带有CRUD方法的服务层 UserController-具有两种方法的控制器:get和update 应用程序-主类(带有@EnableTransactionManag

因此,我有一个非常简单的基于Spring Boot的web应用程序

数据库有一个表,
user
,其中包含列
id
username
,还有一条记录:
(1,'Joe')

我还有以下课程:

User
-映射到表
User

UserRepository
-一个Spring数据JPA存储库

UserService
+
DefaultUserService
-带有CRUD方法的服务层

UserController
-具有两种方法的控制器:
get
update

应用程序
-主类(带有
@EnableTransactionManagement
注释 (在上面)

所以,我要做的是测试事务隔离级别
READ\u COMMITTED
。我同时发送两个请求:

  • /update
    ,它更新用户,将其名称设置为
    Jack
    ,然后将当前线程休眠5s,然后提交事务
  • /get
    ,每次尝试后重复读取同一用户10次,并小睡1秒
  • 问题是,即使在提交了(1)的事务之后,(2)仍会继续返回旧值-
    Joe
    。如果在此之后我尝试向
    /get
    发送另一个请求,它将返回
    Jack
    ,正如预期的那样,因此只有在事务(1)向数据库提交更改之前事务(2)已启动时,才会发生此问题

    您可以在下面找到一些代码供参考

    服务:

    @Service
    public class DefaultUserService implements UserService {
    
        ... //fields & constructor
    
        @Override
        @SneakyThrows
        @Transactional(isolation = Isolation.READ_COMMITTED)
        public User read(Long id) {
            User user = userRepository.findById(id).get();
            Thread.sleep(1000);
            return user;
        }
    
        @Override
        @SneakyThrows
        @Transactional
        public User update(Long id, User update) {
            log.info("Entering update method for user {}", id);
            User user = read(id);
    
            user.setUsername("Jack");
            user = userRepository.save(user);
    
            log.info("User {} updated, falling asleep for 5s", id);
            Thread.sleep(5000);
    
            return user;
        }
    }
    
    控制器:

    @RestController
    public class UserController {
    
        ... //fields & constructor
    
        @RequestMapping("/update")
        public User update() {
            User user = userService.update(1L, new User("Jack"));
            log.info("UPDATED: {}", user);
            return user;
        }
    
        @RequestMapping("/get")
        public User get() {
            User user = userService.read(1L);
            for (int i = 0; i < 10; i++) {
                log.info("READ: {}", user);
                user = userService.read(1L);
            }
            return user;
        }
    }    
    
    控制器返回的响应也不同。 对于
    /update
    ,它是:

    {"id":1,"username":"Jack"}
    
    而对于
    /get

    {"id":1,"username":"Joe"}
    
    我使用的是MySQL 5.7.18和Spring Boot 2.1.0

    你知道我可能做错了什么/错过了什么吗?
    提前感谢。

    您的代码在
    Oracle
    上正常工作,但在
    MySQL
    上有点不同。阅读这里有关MySql中隔离级别的内容

    我相信,如果您将方法的隔离级别更改为读取提交的,它应该可以正常工作,因为默认情况下MySql具有可重复读取的,处理方式有点不同

       @Transactional(Isolation.READ_COMMITTED)
        public User update(Long id, User update) 
    
    设置:


    好问题。我想知道它是否可能与Spring Boot中默认启用OpenEntityRangeViewFilter有关。您可以设置以下引导属性并重试spring.jpa.open in view=false吗。进一步查看stackoverflow.com/q/30549489/1356423和static.javadoc.io/org.springframework/spring orm/4.0.1.RELEASE/…从下面的评论中,您假设您的问题在于数据库级别而不是JPA级别。启用SQL日志记录将是确定问题所在的良好开端。非常感谢您提出的禁用视图中的open的想法,我一直怀疑控制器开始出问题。我能够使可重复的_-READ按预期使用它。然而,现在我遇到了另一个问题:我无法强制执行读取事务以确保可重复读取。因此,当我为它设置REPEATABLE_READ或甚至SERIALIZABLE isolation时,它会一直返回“Joe”,直到提交第二个事务,然后它开始返回“Jack”(尽管我没有离开该隔离事务的范围!)@AlanHay首先,感谢这个出色的日志库,它很棒。它告诉我,每次调用
    findById
    方法并在返回值后立即提交时,都会打开一个新事务。我知道默认情况下,
    crudepository
    方法是事务性的,但是没有定义传播,所以它应该使用默认方法-
    REQUIRED
    ,我想是吧?无论如何,即使在使用
    @Transactional(propagation=propagation.SUPPORTS)
    (确保没有创建新的trx)注释存储库方法之后,我仍然无法获得有效的可重复读取场景。感谢分享本文。这使我考虑迁移到另一个具有更可预测的隔离机制的DBMS。另外,我尝试将隔离规则应用于更新方法,而不是get,但没有效果,结果是samei意味着您应该对这两种方法保持
    隔离。READ_COMMITTED
    。很不幸,它不起作用。你想解决什么问题?请添加更多信息,而不是在这里抛出一堆愉快的代码行。
       @Transactional(Isolation.READ_COMMITTED)
        public User update(Long id, User update) 
    
    @Override
    @SneakyThrows
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public User read(Long id) {
        User user = userRepository.findById(id).get();
        Thread.sleep(1000);
        return user;
    }