【问题标题】:How to protect from concurrent entity creation in Spring JPA如何在 Spring JPA 中防止并发实体创建
【发布时间】:2020-10-10 11:38:31
【问题描述】:

我有以下问题

我的服务,它是 Rest 控制器和 JPA 存储库之间的链接,在在数据库中创建实体之前执行一些检查。 但是出现了下面的问题,如果客户端 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; 

【问题讨论】:

  • 您要执行的规则是什么?所有孩子都必须有唯一的父母?
  • @KenChan,不,问题是如果客户端有一个共同的父节点,那么你必须阻止他们异步创建 SomeEntity,但是如果他们有 不同父母,然后允许

标签: spring database spring-boot concurrency spring-data-jpa


【解决方案1】:

在父节点上启用乐观锁定,并在您开始为父节点创建子节点时使用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

【讨论】:

  • 是的,这种方法有效,谢谢!只有您需要更改 LockModeType.PESSIMISTIC_FORCE_INCREMENT 如果您使用另一个 LockModeType,那么检查仍然会进行,并且锁仅在插入时起作用,我需要它在所有检查之前工作。请更改它,我会选择你的答案作为正确的答案。
  • 是的。我故意这样做是因为你的问题提到你不想使用悲观锁,乐观锁通常应该比悲观锁有更好的性能,除非你有极高的负载会发生冲突。
猜你喜欢
  • 2015-11-09
  • 1970-01-01
  • 2020-08-19
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
  • 1970-01-01
  • 2013-08-30
  • 2016-08-19
相关资源
最近更新 更多