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

Gre*_*pff 49 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();
Run Code Online (Sandbox Code Playgroud)

...但是使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

但我不知道如何指定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
Annotation用于指定执行查询时要使用的LockModeType.在查询方法上使用Query或从方法名称派生查询时,将对其进行评估.

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

如何updateWidgetStock()使用LockModeType.PESSIMISTIC_WRITEset 执行方法?

Oli*_*ohm 67

@Lock从Spring Data JPA 1.6版本开始,CRUD方法支持(事实上,已经有一个里程碑可用).有关详细信息,请参阅此票证.

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

interface WidgetRepository extends Repository<Widget, Long> {

  @Lock(LockModeType.PESSIMISTIC_WRITE)
  Widget findOne(Long id);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 啊好的,在1.4.1中看到它正在工作时我将它添加到一个被覆盖的findOne,但不是当我添加它时我自己的findByXxx.主要问题仍然是,我如何在某些时间使用锁定而不是所有时间. (3认同)

pet*_*cek 16

如果您不想覆盖标准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);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您正在使用PostgreSQL,当您想要设置锁定超时以避免死锁时,事情会变得有点复杂.PostgreSQL忽略javax.persistence.lock.timeoutJPA属性或@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;
}
Run Code Online (Sandbox Code Playgroud)

}

  • 不幸的是,这不会刷新`entityManager`缓存.你必须手动执行`entityManager.refresh(wallet)` (3认同)

Pac*_*ace 14

如果您能够使用Spring Data 1.6或更高版本而忽略此答案并参考Oliver的答案.

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

如果你想实现自己的解决方案,你可能会.在引擎盖下@Lock处理注释,通过LockModePopulatingMethodIntercceptor该注释执行以下操作:

TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);
Run Code Online (Sandbox Code Playgroud)

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

  • 如果我理解正确的话,我可以通过让`findOne`使用一个悲观的锁来实现我想要的效果 - 该锁应该保持到我的事务结束(它将在我的`updateWidgetStock()`方法结束时结束.是对的吗? (2认同)
  • 注意,在即将发布的Spring Data JPA 1.6版本中,CRUD方法将支持使用`@Lock`.有关详细信息,请参阅此[ticket](https://jira.spring.io/browse/DATAJPA-173). (2认同)