首页 > 解决方案 > 如何在 Spring JPA 中防止并发实体创建

问题描述

我有以下问题

我的服务是 Rest 控制器和 JPA Repository 之间的链接,在数据库中创建实体之前会执行一些检查。但是出现了下面的问题,如果客户端 1 和客户端 2 有一个共同的逻辑父节点,并且同时发送一个创建实体的请求,那么它们很可能同时通过检查,并且能够创建一个实体理论上不应该创建,如何避免这个问题?此外,如果客户有不同的父级,那么他们可以在任何情况下创建这些实体。

有一个想法是如何解决这个问题,在创建实体之前需要在创建实体之前获取父行上的锁,然后第二个客户端在尝试创建实体时会出错,但是如何实现Spring JPA 中的这种方法?感谢。

为了更好地理解,我举个例子:

public class Parent {
    @Id
    Long id;
    @OneToMany
    @JoinColumn(name = "parent_id")
    private List<Child> childs;
}

所有请求实体创建的客户端都有一些父服务和创建东西的方法的大致代码:

public SomeEntity createSomeEntity(SomeEntity someEntity) {
    // further, some checks are made on these lines
    // checks
    if (checks are passed) {
        someEntityRepository.save(someEntity);
    } else {
        // entity will not be created in the database
    }
}

如果客户有不同的父母,那么没有问题,但如果他们有一个父母,那么假设两个客户可能同时通过检查并创建冲突的实体。

同时,我不想让这段代码同步或每次都使用悲观锁定创建 SomeEntity ,因为对于不同祖先的客户端来说,多线程会丢失。 正如我所说,我的想法是,如果在签入服务类获取父行上的锁之前,可以解决这个问题,那么我们不会失去并行性以及具有共同父级的客户端创建一个应该不被排除,但我不知道如何使用 Spring JPA

使用 PostgreSQL 数据库版本 12.2

如果我要使用 SQL 代码执行此操作,则在此位置,我们将 SomeEntity 实体同步添加到其父级 id = 1 的客户端:

START TRANSACTION;
SELECT * FROM parent WHERE id = 1 FOR UPDATE;
-- here is the logic for adding SomeEntity 
COMMIT; 

标签: springdatabasespring-bootconcurrencyspring-data-jpa

解决方案


在 parent 上启用乐观锁定,并 OPTIMISTIC_FORCE_INCREMENT在您开始为该父级创建子级时使用锁定模式锁定父级。

它将确保如果多个线程同时为同一个父级创建子级,则只有一个线程会成功,其他线程会失败。对于失败的,只需抓住OptimisticLockException并重试。

请注意,如果另一个线程同时尝试更新父级的内容,也会被视为冲突。因此,从技术上讲,对于您的用例,它将确保只有一个线程将为同一个父级创建子级,或修改同一个父级。

明智的代码看起来像:

@Entity
public class Parent {


  @Version
  private long version;

}

以及锁定父级的代码:

Parent parent = entityManager.find(Parent.class, 1L);
entityManager.lock(parent, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

// And use this parent to create the children as usual

推荐阅读