使用Spring Data JPA查找实体时如何启用LockModeType.PESSIMISTIC_WRITE?

时间:2013-04-23 01:27:51

标签: java spring jpa spring-data spring-data-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方法中的所有内容都应该使用悲观的锁定集来完成。

有一个Spring Data JPA注释org.springframework.data.jpa.repository.Lock允许您设置LockModeType,但我不知道将它放在updateWidgetStock方法上是否有效。这听起来更像是WidgetRepository上的注释,因为Javadoc说:

  

org.springframework.data.jpa.repository
  @Target(value = METHOD)
  @Retention(value = RUNTIME)
  @Documented
  public @interface Lock
  注释用于指定在执行查询时要使用的LockModeType。在查询方法上使用Query时,或者从方法名称派生查询时,将对其进行评估。

...所以这似乎没有帮助。

如何使用updateWidgetStock()设置执行LockModeType.PESSIMISTIC_WRITE方法?

3 个答案:

答案 0 :(得分:62)

从Spring Data JPA 1.6版开始,CRUD方法支持

@Lock(事实上,已经有milestone可用)。有关详细信息,请参阅此ticket

使用该版本,您只需声明以下内容:

interface WidgetRepository extends Repository<Widget, Long> {

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

这将导致支持存储库代理的CRUD实现部分将已配置的LockModeType应用于find(…)上的EntityManager调用。

答案 1 :(得分:14)

如果您不想覆盖标准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属性或javax.persistence.lock.timeout注释中设置的标准属性@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;
}

}

答案 2 :(得分:13)

如果您能够使用Spring Data 1.6或更高版本而忽略此答案,请参阅Oliver的回答。

Spring Data悲观@Lock注释仅适用于(如您所指出的)查询。我知道没有可以影响整个交易的注释。您可以创建findByOnePessimistic方法,使用悲观锁定来调用findByOne,也可以将findByOne更改为始终获得悲观锁定。

如果你想实现自己的解决方案,你可能会。在@Lock下,LockModePopulatingMethodIntercceptor注释由TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode); 处理,执行以下操作:

ThreadLocal<LockMode>

您可以创建一个具有bindResource成员变量的静态锁管理器,然后在每个存储库中的每个方法都包含一个方面,该方法调用ThreadLocal并在{{1}中设置锁定模式}。这将允许您基于每个线程设置锁定模式。然后,您可以创建自己的@MethodLockMode注释,该方法将方法包装在一个方面,该方面在运行方法之前设置特定于线程的锁定模式,并在运行该方法后将其清除。