Как включить 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?
Ответы
Ответ 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;
}
}