このコードの同等物をどのように達成できますか:
_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()
メソッドを実行するにはどうすればよいですか?
_@Lock
_は、Spring Data JPAのバージョン1.6の時点でCRUDメソッドでサポートされています(実際、既に milestone が利用可能です)。詳細については、こちらをご覧ください ticket .
そのバージョンでは、次を宣言するだけです。
_interface WidgetRepository extends Repository<Widget, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Widget findOne(Long id);
}
_
これにより、バッキングリポジトリプロキシのCRUD実装部分は、構成されたLockModeType
をEntityManager
のfind(…)
呼び出しに適用します。
標準の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;
}
}
この回答を無視して、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
アノテーションを作成して、メソッドを実行する前にスレッド固有のロックモードを設定し、メソッドの実行後にクリアするアスペクトでメソッドをラップします。