How to enable LockModeType.PESSIMISTIC_WRITE when looking up entities with Spring Data JPA?

后端 未结 3 1830
难免孤独
难免孤独 2020-12-02 18:27

How can I achieve the equivalent of this code:

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


        
相关标签:
3条回答
  • 2020-12-02 18:36

    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.

    0 讨论(0)
  • 2020-12-02 18:39

    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;
    }
    

    }

    0 讨论(0)
  • @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.

    0 讨论(0)
提交回复
热议问题