54

如何实现此代码的等效项:

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允许您设置 a LockModeType,但我不知道将它放在updateWidgetStock方法上是否有效。这听起来更像是 上的注释WidgetRepository,因为 Javadoc 说:

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

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

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

4

3 回答 3

75

@Lock从 Spring Data JPA 1.6 版开始,CRUD 方法支持(事实上,已经有一个里程碑可用)。请参阅此以获取更多详细信息。

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

interface WidgetRepository extends Repository<Widget, Long> {

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

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

于 2014-04-23T06:14:19.470 回答
21

如果您不想覆盖标准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 忽略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;
}

}

于 2015-08-31T16:29:11.673 回答
15

如果您能够使用 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注释,将方法包装在一个方面,该方面在运行该方法之前设置特定于线程的锁定模式,并在运行该方法后将其清除。

于 2013-04-25T03:50:08.363 回答