JPA Optimistic Locking: как предотвратить удаление, если запись была обновлена ​​и наоборот? - PullRequest
1 голос
/ 13 мая 2019

Мне нужно, чтобы мое приложение имело следующее поведение:

Сценарий 1

  1. Пользователь A просматривает заказ.

  2. Пользователь B просматривает тот же заказ.

  3. Пользователь A удаляет заказ.
  4. Пользователь B запрашивает обновление заказа.Это должно завершиться неудачей.

Сценарий 2

  1. Пользователь A просматривает заказ
  2. Пользователь B просматривает тот же заказ
  3. Обновления пользователя Aзаказ.
  4. Пользователь Б запрос на удаление заказа.Это должно завершиться ошибкой.

Используя JPA (Hibernate через Spring Data JPA), я пытаюсь использовать @Version для реализации этого оптимистического поведения блокировки:

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @Version
    private Integer version;

    // many other fields

При удаленииклиент UI предоставляет серверу список идентификаторов заказов вместе с номером версии для каждого идентификатора заказа.В этом [сообщении ( Spring Data JPA: удалить семантику оптимистической блокировки ) упоминается стандартное решение:

if (entity.getVersion() != dto.getVersion()) {
    throw new OptimisticLockException("...");
}

Чтобы использовать это, мне нужно

  1. lookupобъект из базы данных использует идентификатор заказа от клиента
  2. сравните версию объекта с версией DTO от клиента
  3. Выполните удаление.

Проблема в том, что на шаге 2 сущность и версия DTO могут совпадать.Но тогда на шаге 3 версия может отличаться.Есть ли способ заставить hibernate выполнить проверку и обновление как одно атомарное действие, такое как:

 delete from [Order] where orderId = ? and version = ? 

и бросить StaleObjectStateException, если ни один не удален.

Обновление

Я нашел два подхода, которые должны работать.Есть ли проблема с одним из этих двух подходов?Второй подход предполагает меньше поездок в базу данных.Как правило, клиент отправляет только один заказ на удаление за раз, поэтому производительность здесь не должна быть проблемой.

Подход 1

Для каждого удаляемого заказа:

        Order order = orderRepository.findById(
                orderIdFromClient).orElseThrow(() ->
            new OptimisticLockException());

        if (!order.getVersion().equals(versionFromClient)) {
            throw new OptimisticLockException();
        }

        // We now know the managed entity has the same version
        // as the requested version. If some other transaction
        // has changed the entity, Hibernate will rollback and
        // throw OptimisticLockException.
        orderRepository.delete(order);

Подход 2

Добавить метод репозитория Order:

int deleteByIdAndVersion(Long id, Integer version);

Для каждого удаляемого заказа:

        int x = orderRepository.deleteByIdAndVersion(orderIdFromClient, versionFromClient);
        if (x==0) {
            throw new OptimisticLockException();
        }
...