Когда, если вообще, можно ли предположить атомарность изменений данных в двух параллельных методах, аннотированных @Transactional? - PullRequest
0 голосов
/ 27 марта 2020

Для простоты возьмем таблицу базы данных storage в моем Postgres с одним полем resources. При использовании запланированной задачи объем ресурсов для нескольких записей постоянно обновляется следующим образом:

@Scheduled(fixedDelay = 500)
@Transactional
public void tick() {
  // ...
  storageService.findAll().stream()
      // This method call in map is highly simplified for this post
      // Assume it increments all storages by one per tick of the scheduled method
      .map(storage -> storage.withResources(storage.getResources() + 1))
      .forEach(storageService::update);
  // ...
}

Соответствующая служба основана на DAO, сгенерированных jOOQ:

@Service
@RequiredArgsConstructor
public class StorageServiceImpl implements StorageService {
  private final StorageDao dao;

  @Override
  public List<Storage> findAll() {
    return dao.findAll();
  }

  @Override
  public void update(final Storage storage) {
    dao.update(storage);
  }
}

Исходный фрагмент работает в предположении, что между вызовами findAll и update не происходит никаких изменений в выбранных строках (в основном все в этой конкретной таблице), в противном случае возникает аномалия и ресурсы, скорее всего, отсутствуют.

Теперь у меня есть контроллер, который предоставляет конечную точку для расходования этих хранимых ресурсов. Я уже реализовал блокировку Redis для предотвращения множественных параллельных вызовов этой конечной точки. В этой цепочке операций на этапе выделения ресурсов, которые необходимо потратить, я аналогичным образом сокращаю количество ресурсов. Что-то вроде этого:

@Transactional
// ...
storageService.findById(id) // Optional<Storage>
    .map(storage -> storage.withResources(storage.getResources() - costs)
    .map(storageService::update)
    .orThrow(IllegalStateException::new);

В этот момент мне было любопытно, работает ли (вообще) это правильно, параллельно с моей запланированной задачей или нет. В основном ставлю под сомнение мое использование @Transactional. Как я могу проверить правильность? Или необходимо вручную написать запрос jOOQ для блокировки строк с помощью SELECT ... FOR UPDATE?

...