Подтвердить что ты не робот

Как включить LockModeType.PESSIMISTIC_WRITE при поиске объектов с помощью Spring данных JPA?

Как я могу получить эквивалент этого кода:

tx.begin();
Widget w = em.find(Widget.class, 1L, LockModeType.PESSIMISTIC_WRITE);
w.decrementBy(4);
em.flush();
tx.commit();

... но используя аннотации Spring и Spring -Data-JPA?

В основе моего существующего кода:

@Service
@Transactional(readOnly = true)
public class WidgetServiceImpl implements WidgetService
{
  /** The spring-data widget repository which extends CrudRepository<Widget, Long>. */
  @Autowired
  private WidgetRepository repo;

  @Transactional(readOnly = false)
  public void updateWidgetStock(Long id, int count)
  {
    Widget w = this.repo.findOne(id);
    w.decrementBy(4);
    this.repo.save(w);
  }
}

Но я не знаю, как указать, что все в методе updateWidgetStock должно выполняться с помощью пессимистического набора блокировок.

Существует аннотация , которая позволяет вам установить LockModeType, но я не знаю, действительно ли оно положено в метод updateWidgetStock. Это больше похоже на аннотацию на WidgetRepository, потому что Javadoc говорит:

org.springframework.data.jpa.repository
@Target (value = МЕТОД)
@Retention (value = RUNTIME)
@Документированный
public @interface Lock
Аннотации, используемые для указания типа LockModeType для использования при выполнении запроса. Он будет оцениваться при использовании запроса в методе запроса или вывести запрос из имени метода.

... так что это не кажется полезным.

Как я могу выполнить мой метод updateWidgetStock() с помощью LockModeType.PESSIMISTIC_WRITE set?

4b9b3361

Ответ 1

@Lock поддерживается по методам CRUD с версии 1.6 из Spring Data JPA (на самом деле уже существует milestone). Подробнее см. Этот .

С этой версией вы просто объявляете следующее:

interface WidgetRepository extends Repository<Widget, Long> {

  @Lock(LockModeType.PESSIMISTIC_WRITE)
  Widget findOne(Long id);
}

Это приведет к тому, что часть реализации CRUD прокси-сервера репозитория поддержки применит настроенный вызов LockModeType к вызову find(…) на EntityManager.

Ответ 2

Если вы можете использовать Spring Data 1.6 или выше, чем игнорировать этот ответ и ссылаться на ответ Оливера.

Аннотации Spring Пессимистические данные @Lock применяются только (как вы указали) к запросам. Я не знаю, какие аннотации могут повлиять на всю транзакцию. Вы можете либо создать метод findByOnePessimistic, который вызывает findByOne с пессимистической блокировкой, либо вы можете изменить findByOne, чтобы всегда получать пессимистическую блокировку.

Если вы хотите реализовать свое собственное решение, вы, вероятно, могли бы. Под капотом аннотация @Lock обрабатывается LockModePopulatingMethodIntercceptor, которая выполняет следующие действия:

TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);

Вы можете создать некоторый статический менеджер блокировок, который имеет переменную-член ThreadLocal<LockMode>, а затем иметь аспект, обернутый вокруг каждого метода в каждом репозитории, который вызвал bindResource с режимом блокировки, установленным в ThreadLocal. Это позволит вам установить режим блокировки по каждому потоку. Затем вы можете создать свою собственную аннотацию @MethodLockMode, которая будет обертывать метод в аспекте, который устанавливает режим блокировки, специфичный для потока, перед запуском метода и очищает его после запуска метода.

Ответ 3

Если вы не хотите переопределять стандартный метод findOne(), вы можете получить блокировку в своем настраиваемом методе с помощью запроса select ... for update так:

/**
 * Repository for Wallet.
 */
public interface WalletRepository extends CrudRepository<Wallet, Long>, JpaSpecificationExecutor<Wallet> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select w from Wallet w where w.id = :id")
    Wallet findOneForUpdate(@Param("id") Long id);
}

Однако, если вы используете PostgreSQL, все может немного усложниться, если вы хотите установить тайм-аут блокировки, чтобы избежать взаимоблокировок. PostgreSQL игнорирует стандартное свойство javax.persistence.lock.timeout, заданное в свойствах JPA, или в аннотации @QueryHint.

Единственным способом, с помощью которого я мог работать, было создание пользовательского репозитория и установка таймаута вручную перед блокировкой объекта. Это не приятно, но, по крайней мере, это работает:

public class WalletRepositoryImpl implements WalletRepositoryCustom {

@PersistenceContext
private EntityManager em;


@Override
public Wallet findOneForUpdate(Long id) {
    // explicitly set lock timeout (necessary in PostgreSQL)
    em.createNativeQuery("set local lock_timeout to '2s';").executeUpdate();

    Wallet wallet = em.find(Wallet.class, id);

    if (wallet != null) {
        em.lock(wallet, LockModeType.PESSIMISTIC_WRITE);
    }

    return wallet;
}

}