Java Spring数据JPA:提交了隔离读取的事务在另一个事务中看不到提交的数据
因此,我有一个非常简单的基于Spring Boot的web应用程序 数据库有一个表,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
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秒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;
}