Spring @Transactional одна транзакция метода за раз - PullRequest
0 голосов
/ 25 октября 2018

Требуемое решение:

Каждый год учетные записи регистрируются в базе данных и получают уникальный номер от 1 до n.После окончания года следующий год будет повторяться от 1 до n при регистрации учетных записей.

У меня есть таблица, в которой хранится (пример):

id     , available_number, current_year
someId0, 8000            , 2000
someId1, 2500            , 2001

Фрагмент метода:

@Transactional
public AccountNumber getAndIncreaseCorrectAccountNumber(String current_year) {
    AccountNumber accountNumber;
    Optional<AccountNumber> foundAccountNumber = Optional.ofNullable(AccountNumberRepository.findByYear(current_year));
    if(!foundAccountNumber .isPresent()) {
        accountNumber = new AccountNumber();
        accountNumber .setAvailableNumber(1L);
        accountNumber .setCurrentYear(current_year);
    } else {
        accountNumber = foundAccountNumber.get();
        accountNumber.setAvailableNumber(accountNumber.getAvailableNumber() + 1);
    }
    return accountRepository.save(accountNumber);
}

Проблема:

Поскольку несколько учетных записей регистрируются в последний день (последнюю минуту), учетные записи получают одинаковые available_number.Заметил, что 4 учетные записи регистрируются с одинаковым available_number в 1 секунду (с разницей в ~ 0,1 секунды между регистрациями)

Я думаю, что это связано с тем, что одна транзакция началась, затем другая взломана, а одна не завершенас getting и saving другим gets таким же available_number

Думая, как решить:

  1. Прочитал о Isolation иSERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); предотвращает взаимодействие транзакций с eatch other, но не мешает двум транзакциям получать одинаковые available_number.Также прочитайте о Propagation и REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), что делает id, если новая транзакция появляется, когда предыдущая не завершена, затем предыдущая блокируется, затем новая завершается, а после этого сначала размораживается и завершается.Что не ясно, так это то, что в моем случае первая транзакция может получить значение, затем она зависает, а затем, после того как новая транзакция выполнена с тем же available_number, первая транзакция отменяется и заканчивается с тем же available_number (или я здесь неправильно понимаю?)
  2. Другой вариант - каким-то образом преобразовать этот метод в один вызов SQL.Для записи хотя бы nativeQuery update выберите, который имеет логику и выполняет хранение и возврат объекта (какие-либо предложения?).Этого пути должно быть достаточно только @Transactional?
  3. Есть ли способ хранить метод Transactions в стеке и делать по одному (после чего первая транзакция закрывается, а новая открывается)?

Подвопрос: есть ли способ имитировать множественный запрос @test и проверять, работает ли новое решение потенциально?(регистрация нового аккаунта произойдет только в следующем году)

1 Ответ

0 голосов
/ 28 ноября 2018

Как писал @JBNizet, а я читал в других статьях, OptimisticLock решает проблему, потому что она позволяет только одной транзакции, которая изменяет строку, чтобы быть успешной.Другие транзакции выбрасываются с ObjectOptimisticLockingFailureException.Затем я использовал try{}catch{}, чтобы сделать рекурсию на ObjectOptimisticLockingFailureException, чтобы перенаправить обратно к тому же методу

Хитрость заключалась в том, что логика getAndIncreaseCorrectAccountNumber была в глубоком слое, где в той же транзакции много другихЛогика CRUD произошла, поэтому мне нужно было потянуть мой try{}catch{} чуть выше всего верхнего слоя @Transaction (иначе основной объект был изменен и не был откат, поэтому try{}catch{}, кажется, работает, когда он находится выше самого верхнего @Transaction)

В коде, что я сделал:

  1. В AccountNumber добавлена ​​версия строки PostgreSQL для OptimisticLock

@ Column (имя= "version_num") @Version private int version;

В слое хранилища AccountNumberRepository добавлено @Lock(LockModeType.OPTIMISTIC) чуть выше метода

Добавлено try{}catch{} для перехвата OptimisticLock и включения повторения для того же метода

public String submitSomeObjectWithRecursionHandling(@NotNull SomeObject someObject, @NotBlank String accountId){
        try {
        return submitSomeObject(someObject, accountId);
        } catch (ObjectOptimisticLockingFailureException | OptimisticLockException e) {
            e.printStackTrace();
            return submitSomeObjectWithRecursionHandling(someObject, accountId);
        }
    }

submitSomeObject(someObject, accountId) запущено @Transaction

Чего мне не удалось добиться, так это написать интеграционный тест.Итак, я создал 10 из подготовленных к отправке SomeObects и из Front-End сразу отправил запрос на отправку, и он сначала воспроизвел ошибку с повторной копией accountNumber, а позже, после исправления, показал, что рекурсия обрабатывает проблему

Если у вас есть несколько способов протестировать это решение в интеграционном тесте, сообщите мне.

...