How to enable LockModeType.PESSIMISTIC_WRITE when looking up entities with Spring Data JPA?
@Lock
is supported on CRUD methods as of version 1.6 of Spring Data JPA (in fact, there's already a milestone available). See this ticket for more details.
With that version you simply declare the following:
interface WidgetRepository extends Repository<Widget, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Widget findOne(Long id);
}
This will cause the CRUD implementation part of the backing repository proxy to apply the configured LockModeType
to the find(…)
call on the EntityManager
.
If you don't want to override standard findOne()
method, you can acquire a lock in your custom method by using select ... for update
query just like this:
/**
* 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);
}
However, if you are using PostgreSQL, things can get a little complicated when you want to set lock timeout to avoid deadlocks. PostgreSQL ignores standard property javax.persistence.lock.timeout
set in JPA properties or in @QueryHint
annotation.
The only way I could get it working was to create a custom repository and set timeout manually before locking an entity. It's not nice but at least it's working:
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;
}
}
If you are able to use Spring Data 1.6 or greater than ignore this answer and refer to Oliver's answer.
The Spring Data pessimistic @Lock
annotations only apply (as you pointed out) to queries. There are not annotations I know of which can affect an entire transaction. You can either create a findByOnePessimistic
method which calls findByOne
with a pessimistic lock or you can change findByOne
to always obtain a pessimistic lock.
If you wanted to implement your own solution you probably could. Under the hood the @Lock
annotation is processed by LockModePopulatingMethodIntercceptor
which does the following:
TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);
You could create some static lock manager which had a ThreadLocal<LockMode>
member variable and then have an aspect wrapped around every method in every repository which called bindResource
with the lock mode set in the ThreadLocal
. This would allow you to set the lock mode on a per-thread basis. You could then create your own @MethodLockMode
annotation which would wrap the method in an aspect which sets the thread-specific lock mode before running the method and clears it after running the method.