Javaにおける楽観的オフラインロックパターン:データベーストランザクションにおける競合解決をマスターする
別名
- 楽観的同時実行制御
楽観的オフラインロックのデザインパターンの意図
Javaにおける楽観的オフラインロックパターンは、長時間のデータベースロックを必要とせずに同時データ変更を管理するために特別に設計されており、システムのパフォーマンスとスケーラビリティを向上させます。
現実世界の例を交えた楽観的オフラインロックパターンの詳細な説明
現実世界の例
複数のユーザーが本の貸し出しや返却を行う図書館を想像してください。ユーザーが本を閲覧したり、借りるかどうかを決定している間、各本をロックする代わりに、図書館は楽観的なアプローチを使用します。各本にはタイムスタンプまたはバージョン番号があります。ユーザーが本を借りることを決定すると、本のバージョン番号を確認します。それが現在のバージョンと一致する場合、トランザクションは続行されます。別のユーザーがその間に本を借りて、バージョンが不一致になった場合、最初のユーザーは再試行するように通知されます。このアプローチにより、複数のユーザーが同時に本を閲覧して借りることを試みることができ、カタログ全体をロックすることなく、図書館の効率とユーザー満足度を向上させます。
平易な言葉で
楽観的オフラインロックパターンは、ロックなしでトランザクションを進めることを許可し、パフォーマンスとスケーラビリティを向上させるために、競合が発生した場合にのみ解決することにより、同時データ変更を管理します。
Wikipediaによると
楽観的同時実行制御(OCC)(楽観的ロックとも呼ばれる)は、関係データベース管理システムやソフトウェアトランザクショナルメモリなどのトランザクションシステムに適用される同時実行制御手法です。
Javaにおける楽観的オフラインロックパターンのプログラム例
このセクションでは、Javaにおける楽観的オフラインロックの実践的な実装について詳しく説明します。これらの手順に従うことで、アプリケーションが最小限のオーバーヘッドでデータの競合と同時実行を処理できるようになります。
楽観的オフラインロックパターンは、競合が発生した場合にのみ解決し、ロックなしで複数のトランザクションを進めることを許可する同時実行制御手法です。このパターンは、競合するトランザクションの可能性が低く、長時間のロックがパフォーマンスとスケーラビリティを妨げる可能性があるシナリオで役立ちます。
まず、お金の合計とバージョン番号を持つ銀行カードを表すCard
エンティティがあります。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Card {
private long id;
private long personId;
private float sum;
private int version;
}
CardUpdateService
クラスはUpdateService
インターフェースを実装し、Card
エンティティを更新するメソッドdoUpdate
を提供します。doUpdate
メソッドは、最初にCard
エンティティの現在のバージョンを取得します。次に、Card
のお金の合計を更新するためのビジネスロジックを実行します。データベースでCard
を更新する前に、データベース内のCard
のバージョンが、最初に取得した初期バージョンと同じであるかどうかを確認します。バージョンが一致する場合、更新を続行します。バージョンが一致しない場合、それは別のトランザクションがその間にCard
を更新したことを意味し、ApplicationException
をスローします。
@RequiredArgsConstructor
public class CardUpdateService implements UpdateService<Card> {
private final JpaRepository<Card> cardJpaRepository;
@Override
@Transactional(rollbackFor = ApplicationException.class) //will roll back transaction in case ApplicationException
public Card doUpdate(Card card, long cardId) {
float additionalSum = card.getSum();
Card cardToUpdate = cardJpaRepository.findById(cardId);
int initialVersion = cardToUpdate.getVersion();
float resultSum = cardToUpdate.getSum() + additionalSum;
cardToUpdate.setSum(resultSum);
//Maybe more complex business-logic e.g. HTTP-requests and so on
if (initialVersion != cardJpaRepository.getEntityVersionById(cardId)) {
String exMessage = String.format("Entity with id %s were updated in another transaction", cardId);
throw new ApplicationException(exMessage);
}
cardJpaRepository.update(cardToUpdate);
return cardToUpdate;
}
}
このコードスニペットでは、CardUpdateServiceクラスのdoUpdateメソッドは、楽観的オフラインロックパターンのプログラム例です。これにより、Cardエンティティをロックなしで更新し、更新前にCardのバージョンを確認することで競合を解決できます。
Javaで楽観的オフラインロックパターンを使用する場合
- 複数のトランザクションが、データの一貫性を損なうことなく、同時に同じデータにアクセスして変更する必要がある場合。
- 競合するトランザクションの可能性が低いシステム。
- パフォーマンスとスケーラビリティを妨げる可能性のある長時間のロックを回避したい場合。
楽観的オフラインロックパターンのJavaチュートリアル
Javaにおける楽観的オフラインロックパターンの現実世界での応用
- 高読み取り、低書き込みアクセスパターンのWebベースアプリケーション。
- リソースを長期間ロックすることが実現可能ではない分散システム。
- データ永続化のためにJPAまたはHibernateを使用するJavaエンタープライズアプリケーション。
楽観的オフラインロックパターンの利点とトレードオフ
利点
- リソースのロックの必要性が減り、パフォーマンスが向上します。
- より多くのトランザクションを同時に進めることができるようにすることで、システムのスケーラビリティが向上します。
- 競合が発生した場合にのみ処理することで、トランザクション管理が簡素化されます。
トレードオフ
- 競合を処理するために追加のロジック(バージョン管理、ロールバック/再試行)が必要になり、アプリケーションコードが複雑になる可能性があります。
- 競合が頻繁に発生する場合は、トランザクションの再試行がより頻繁になる可能性があります。
- 頻繁なデータ変更の衝突が発生する高競合シナリオには適していません。
関連するJavaデザインパターン
- 悲観的オフラインロック:楽観的オフラインロックとは異なり、このパターンはトランザクション全体でデータをロックすることにより、競合を防ぐためにロックを使用します。これは高競合シナリオで役立ちます。
- ユニットオブワーク:一連の変更を単一のトランザクションとして管理し、データ整合性を確保するのに役立ちます。これは、複雑なトランザクションを処理するために、楽観的オフラインロックと組み合わせて使用できます。
- バージョン番号:各データエンティティのバージョン番号を維持することにより、競合を検出するために楽観的オフラインロックで使用される一般的な手法。