Java 记录似乎在连续的数据库操作之间被锁定(Spring数据、Hibernate)
我正在经历一种似乎是连续(而非并发)数据库操作之间的记录锁的情况,我无法解释这一点 形势 从REST控制器调用方法Java 记录似乎在连续的数据库操作之间被锁定(Spring数据、Hibernate),java,hibernate,spring-data,Java,Hibernate,Spring Data,我正在经历一种似乎是连续(而非并发)数据库操作之间的记录锁的情况,我无法解释这一点 形势 从REST控制器调用方法saveRegistrationToken。我通过Postman(HTTP客户端)测试对该方法的调用;该方法不会在其他任何地方调用,这是唯一执行的操作 方法执行的行为应如下所示: @Override public void saveRegistrationToken(String userId, String registrationToken) { usersReposit
saveRegistrationToken
。我通过Postman(HTTP客户端)测试对该方法的调用;该方法不会在其他任何地方调用,这是唯一执行的操作
方法执行的行为应如下所示:
@Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
String check = usersRepository.findById(userId).get().getRegistrationToken();
/* breakpoint on the following line */
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}
- 将字符串(注册令牌)和用户ID(也是字符串)作为输入
- 它应该首先更新一个USERS表,将REGISTRATION_TOKEN列的值设置为null,以便每一行中该列的值与输入注册令牌的值相同
- 然后,它应该使用指定的用户ID更新该行的用户表,将REGISTRATION_TOKEN列的值设置为input REGISTRATION TOKEN
- 方法的每一次首次执行都将按预期进行:将DB列注册\ U标记(表用户)的值设置为null,无论该值是指定的值,然后将注册标记设置为具有输入用户ID的行的输入值。因此,所讨论行的注册令牌的值是方法执行结束时的输入值
- 每秒钟执行一次将正确执行第一步(“作废”注册令牌,无论其存在何处),但不会使用指定的用户ID更新该行的值。因此,该行的注册令牌值在方法执行结束时为空
- 我最初认为这将是一个刷新问题:一旦注册令牌被设置为null,事务将不会被刷新,直到注册令牌被再次设置为用户ID之后,这将导致两个DB操作之间的行为冲突。我反驳了显式调用
usersRepository.flush()代码>在第一次操作后,观察相同的行为
- 我在存储库操作上尝试了不同的传播和隔离级别:
,但没有帮助@Transactional(传播=propagation.SUPPORTS,隔离=isolation.READ\u UNCOMMITTED)
- 我尝试在存储库操作上显式设置刷新模式:
,这没有改变任何东西@QueryHints(value={@QueryHint(name=org.hibernate.annotations.QueryHints.flush_mode,value=“ALWAYS”)})
- 在我看来,第一个操作“锁定”了更新后的记录,这会阻止第二个操作更新记录,但我不明白怎么做
- 显式指定自动提交true:
spring.datasource.auto commit=true
compile(“org.springframework.boot:springbootstarterdatajpa”)
version2.1.1.RELEASE
任何想法、解释、文档链接都将不胜感激——我已经尝试了我能想到的一切
非常感谢,克里斯
更新:
另一个原因,我认为这是某种冲水问题
我将此方法更新如下:
@Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
String check = usersRepository.findById(userId).get().getRegistrationToken();
/* breakpoint on the following line */
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}
在指示的断点处停止时:
- 每次第一次(“正常”)执行时,
变量的值为check
null
- 每秒执行一次,其值与输入注册令牌相同
@Transactional
,查看您的代码,但我认为您通过在voidRegistrationToken
中明确定义@Transactional
注释,在方法中定义了适当的事务划分,并使用JpaRepository
提供的方法,以这种方式进行隐式注释
在任何情况下,正如您所指出的,由于对将被分配注册令牌的用户执行不同的操作,您将获得不一致的值
很明显,持久性上下文中由EntityManager
维护的受影响用户
实体的信息在不同方法调用的某个地方受到污染
老实说,我不能给你这种行为的确切原因
这可能与将更改刷新到数据库的时间有关,完全由EntityManager
自行决定,但您已经尝试手动刷新不同的更改,并且您的事务似乎是适当的,因此,这可能不是问题的原因
也许这与@Guillaume建议的二级缓存有关,也许与Spring数据实现@Modifying
操作的方式有关
您可以尝试的一种方法是,在操作完成后,指示您的@修改对持久性上下文的注释:
@修改(clearAutomatically=true)
这将为您提供执行注册令牌更新的干净状态
请看相关的
请注意使用此解决方案可能产生的影响
在EntityManager
中使用flush
和clear
是一种反模式,如果可以,应该通过使用适当的事务划分和组件体系结构来避免这种情况
调用clear
将导致所有对象与EntityManager
解耦。请注意,根据使用情况,即使修改过的对象数据也不会保存到数据库中-这是始终保留modi的flush
的主要区别
@Entity(name = "users")
@AllArgsConstructor //lombok
@Data
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class User {
@Id
private String id;
private String registrationToken;
private String email;
}
@Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
String check = usersRepository.findById(userId).get().getRegistrationToken();
/* breakpoint on the following line */
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}