web-dev-qa-db-ja.com

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-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メソッドのすべてを悲観的なロックセットで実行するように指定する方法がわかりません。

LockModeTypeを設定できるSpring Data JPAアノテーション_org.springframework.data.jpa.repository.Lock_がありますが、updateWidgetStockメソッドに有効であるかどうかはわかりません。 Javadocは次のように言っているので、WidgetRepositoryの注釈のように聞こえます。

org.springframework.data.jpa.repository
@ Target(value = METHOD)
@ Retention(value = RUNTIME)
@文書化
public @interface Lock
クエリの実行時に使用されるLockModeTypeを指定するために使用される注釈。クエリメソッドでクエリを使用する場合、またはメソッド名からクエリを派生する場合に評価されます。

...それは役に立たないようです。

_LockModeType.PESSIMISTIC_WRITE_を設定してupdateWidgetStock()メソッドを実行するにはどうすればよいですか?

48
Greg Kopff

_@Lock_は、Spring Data JPAのバージョン1.6の時点でCRUDメソッドでサポートされています(実際、既に milestone が利用可能です)。詳細については、こちらをご覧ください ticket .

そのバージョンでは、次を宣言するだけです。

_interface WidgetRepository extends Repository<Widget, Long> {

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

これにより、バッキングリポジトリプロキシのCRUD実装部分は、構成されたLockModeTypeEntityManagerfind(…)呼び出しに適用します。

65
Oliver Drotbohm

標準の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;
}

}

15
petr.vlcek

この回答を無視して、Oliverの回答を参照するよりもSpring Data 1.6以降を使用できる場合

Spring Dataの悲観的な@Lock注釈は、(指摘したように)クエリにのみ適用されます。トランザクション全体に影響する可能性のある注釈はありません。悲観的ロックでfindByOnePessimisticを呼び出すfindByOneメソッドを作成するか、findByOneを変更して常に悲観的ロックを取得できます。

独自のソリューションを実装する場合は、おそらく可能です。内部では、@Lock注釈はLockModePopulatingMethodIntercceptorによって処理され、次の処理が行われます。

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

ThreadLocal<LockMode>メンバー変数を持つ静的ロックマネージャーを作成し、bindResourceで設定されたロックモードでThreadLocalを呼び出すすべてのリポジトリのすべてのメソッドをラップするアスペクトを持つことができます。これにより、スレッドごとにロックモードを設定できます。その後、独自の@MethodLockModeアノテーションを作成して、メソッドを実行する前にスレッド固有のロックモードを設定し、メソッドの実行後にクリアするアスペクトでメソッドをラップします。

14
Pace