Java 记录似乎在连续的数据库操作之间被锁定(Spring数据、Hibernate)

Java 记录似乎在连续的数据库操作之间被锁定(Spring数据、Hibernate),java,hibernate,spring-data,Java,Hibernate,Spring Data,我正在经历一种似乎是连续(而非并发)数据库操作之间的记录锁的情况,我无法解释这一点 形势 从REST控制器调用方法saveRegistrationToken。我通过Postman(HTTP客户端)测试对该方法的调用;该方法不会在其他任何地方调用,这是唯一执行的操作 方法执行的行为应如下所示: @Override public void saveRegistrationToken(String userId, String registrationToken) { usersReposit

我正在经历一种似乎是连续(而非并发)数据库操作之间的记录锁的情况,我无法解释这一点

形势

从REST控制器调用方法
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更新该行的值。因此,该行的注册令牌值在方法执行结束时为空
DefaultUserService.java

UsersRepository.java

我尝试过的

  • 我最初认为这将是一个刷新问题:一旦注册令牌被设置为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);
}