Как защититься от одновременного создания сущностей в Spring JPA - PullRequest
0 голосов
/ 20 июня 2020

У меня следующая проблема

Моя служба, которая является связующим звеном между контроллером 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 каждый раз, используя Pessimisti c Блокировка, с тех пор многопоточность теряется для клиентов с разными предками. Как я сказал , У меня была идея, что эту проблему можно решить, если перед проверкой в ​​классе обслуживания получить блокировку на родительской строке, тогда мы не потеряем параллелизм и ситуацию, когда клиенты с общим родительским элементом создают сущность, которую не следует исключать , но как это сделать с Spring JPA, я не знаю

Используется * 1 024 * Версия базы данных 12.2

Если бы я сделал это с кодом SQL, в этом месте мы сделали бы синхронное добавление сущности SomeEntity к клиенту, родительский элемент которого имеет id = 1:

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

1 Ответ

2 голосов
/ 20 июня 2020

Включите optimisti c блокировку для родителя и заблокируйте родителя, используя режим блокировки 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
...