2013-04-23 277 views
23

我怎樣才能做到這一點代碼相當於:如何在使用Spring Data JPA查找實體時啓用LockModeType.PESSIMISTIC_WRITE?

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

...但使用Spring和Spring數據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方法都應該用悲觀鎖組來完成。

有一個Spring數據JPA註釋org.springframework.data.jpa.repository.Lock,它允許您設置LockModeType,但我不知道這是否是有效的把它放在updateWidgetStock方法。這聽起來更像是WidgetRepository註解,因爲Javadoc中說:

org.springframework.data.jpa.repository
@Target(值=法)
@Retention(值= RUNTIME)
@Documented
public @interface Lock
註釋用於指定執行查詢時要使用的LockModeType。當在查詢方法上使用查詢時,或者如果從方法名稱派生查詢時,它將被評估。

...這似乎沒有幫助。

如何讓我的updateWidgetStock()方法執行LockModeType.PESSIMISTIC_WRITE集?

+0

無論這個問題的答案可能會有所幫助:http://stackoverflow.com/questions/11880924/how-to-add-custom-method -to-spring-data-jpa – 2013-04-23 03:42:27

回答

38

@Lock在Spring Data JPA 1.6版的CRUD方法上支持(實際上,已經有milestone可用)。有關更多詳細信息,請參閱此ticket

與該版本只需聲明如下:

interface WidgetRepository extends Repository<Widget, Long> { 

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

這將導致後備資源庫代理配置LockModeType適用於find(…)呼叫的EntityManager的CRUD實現部分。

+0

這個@Lock已經在我的測試中與spring-data-jpa 1.4.1一起工作了......但無論如何,問題是,我怎麼能通過鎖類型?我不想總是使用悲觀鎖,很多senarios是大多數讀取。但是當我知道我在做寫,並且會有爭用我想使用悲觀鎖。我可以創建findOneLock(String,LockModeType)? – 2014-05-28 04:09:48

+2

啊好的,在1.4.1中看到它在工作,當我將它添加到重寫的findOne中時,但不是當我添加它時執行我自己的findByXxx。主要問題仍然存在,我怎麼能在某些時候使用鎖,但並非全部時間。 – 2014-05-28 04:51:15

7

如果您能夠使用Spring Data 1.6或更高版本,請忽略此答案並參考Oliver的答案。

Spring Data悲觀@Lock註釋僅適用於(如您所指出的)查詢。我不知道哪些註釋會影響整個交易。您可以創建一個findByOnePessimistic方法,該方法使用悲觀鎖調用findByOne,或者可以更改findByOne以始終獲得悲觀鎖。

如果你想實現自己的解決方案,你可能可以。引擎蓋下的@Lock註釋由LockModePopulatingMethodIntercceptor處理其執行以下操作:

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

您可以創建其中有一個ThreadLocal<LockMode>成員變量一些靜態鎖管理器,然後在每一個呼籲bindResource倉庫周圍的每一個方法包裹一個方面鎖定模式設置在ThreadLocal。這將允許您以每個線程爲基礎設置鎖定模式。然後,您可以創建自己的@MethodLockMode註釋,該註釋將在方法中包裝該方法,該方法在運行該方法之前設置線程特定的鎖定模式,並在運行該方法之後將其清除。

+0

如果查詢持續到事務結束時(並且因此包含任何以下更新),那麼對查詢有一個悲觀鎖定有什麼意義? – 2013-04-26 00:17:13

+0

在查詢中獲得的悲觀鎖應持續到事務結束。這兩個段落描述瞭如何創建一個事務,其中所有查詢都獲得了悲觀鎖。 – Pace 2013-04-26 13:32:15

+2

如果我理解正確的話,我可以簡單地通過使'findOne'使用悲觀鎖來實現我想要的效果 - 該鎖應該保持到我的事務結束時(這將在我的'updateWidgetStock()'方法結束時結束 – 2013-04-27 06:57:34

5

如果你不希望重寫標準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忽略在JPA屬性或@QueryHint註釋中設置的標準屬性javax.persistence.lock.timeout

我可以使它工作的唯一方法是創建自定義存儲庫並在鎖定實體之前手動設置超時。這不是很好,但至少它的工作:

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

不幸的是,這不會刷新'entityManager'緩存。您必須手動執行'entityManager.refresh(wallet)' – rcomblen 2016-11-11 09:38:45

相關問題