カテゴリ: Spring 更新日: 2025/11/19

Springの楽観ロックと悲観ロックを完全解説!@VersionとLockModeTypeの使い分け入門

楽観/悲観ロックの使い分け:@Version とロックモードの実装手順
楽観/悲観ロックの使い分け:@Version とロックモードの実装手順

先生と生徒の会話形式で理解しよう

生徒

「データ更新の競合ってどうやって防ぐんですか?Springで何か方法があるんでしょうか?」

先生

「ありますよ。Springでは、@Versionを使った楽観ロックや、LockModeTypeを使った悲観ロックで、同時更新の問題を防ぐことができます。」

生徒

「えーと、楽観ロックと悲観ロックってどう違うんですか?」

先生

「それじゃあ、2つの違いや使い分け方、そしてSpringでの実装手順を一緒に学んでいきましょう!」

1. 楽観ロックとは?@Versionを使った競合防止

1. 楽観ロックとは?@Versionを使った競合防止
1. 楽観ロックとは?@Versionを使った競合防止

楽観ロックは、基本的に「他の人が同時に更新しないだろう」という前提で処理を進め、更新時にバージョンが変わっていたら失敗するという仕組みです。Spring Data JPAでは、@Versionアノテーションを使うことで簡単に実装できます。

例えば、商品情報を更新するとき、他の誰かが先に変更していた場合、例外(OptimisticLockingFailureException)が発生し、更新がキャンセルされます。


@Entity
public class Product {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private int stock;

    @Version
    private Long version;

    // getter/setter省略
}

このように@Versionを付けるだけで、バージョン番号による整合性チェックが有効になります。

2. 楽観ロックの処理フローと失敗時の対応

2. 楽観ロックの処理フローと失敗時の対応
2. 楽観ロックの処理フローと失敗時の対応

Spring Bootで楽観ロックを使った場合、以下の流れで動作します:

  • エンティティ取得時にversionを取得
  • 更新処理実行時にversionを比較
  • 一致すれば更新、ずれていれば例外をスロー

このような衝突時には、ユーザーに「再読み込みしてください」といった案内を行う設計が推奨されます。

3. 悲観ロックとは?LockModeTypeで排他制御

3. 悲観ロックとは?LockModeTypeで排他制御
3. 悲観ロックとは?LockModeTypeで排他制御

悲観ロックは、「同時に他の人が更新するかもしれない」という前提で、読み取り時にロックを取得して他のトランザクションを待たせる仕組みです。

Springでは、EntityManagerを使用して明示的にLockModeType.PESSIMISTIC_WRITEを指定することで、悲観ロックを実現できます。


@PersistenceContext
private EntityManager entityManager;

public Product getProductWithLock(Long id) {
    return entityManager.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE);
}

このコードでは、商品データを読み込むと同時に書き込みロックがかかり、他のトランザクションはロックが解放されるまで待たされます。

4. 楽観ロックと悲観ロックの違いと使い分け

4. 楽観ロックと悲観ロックの違いと使い分け
4. 楽観ロックと悲観ロックの違いと使い分け

楽観ロックと悲観ロックは、トランザクションの競合に対する考え方が異なります。下記のような基準で使い分けると良いでしょう。

  • 楽観ロック:更新衝突があまり発生しないケース(例:商品詳細の編集)
  • 悲観ロック:高頻度に競合が発生するケース(例:在庫管理、予約処理)

楽観ロックは非同期処理やWebアプリ向きで、悲観ロックはリアルタイム性が求められるバッチ処理などで多用されます。

5. Spring Data JPAで悲観ロックを使う方法

5. Spring Data JPAで悲観ロックを使う方法
5. Spring Data JPAで悲観ロックを使う方法

Spring Data JPAでも、リポジトリメソッドに@Lockアノテーションを付けることで悲観ロックが使えます。


@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdForUpdate(@Param("id") Long id);

このように指定することで、該当のデータに書き込みロックがかかり、同時アクセスの整合性が保たれます。

6. 楽観ロックで発生する例外の種類と対処

6. 楽観ロックで発生する例外の種類と対処
6. 楽観ロックで発生する例外の種類と対処

@Versionによる楽観ロックでは、以下のような例外が発生する可能性があります。

  • OptimisticLockException(JPA標準)
  • ObjectOptimisticLockingFailureException(Spring)

これらの例外は、catchしてリトライする実装を行うことでユーザーに優しい設計が可能です。


try {
    userService.updateUser(user);
} catch (ObjectOptimisticLockingFailureException e) {
    // ログを記録して再読み込みを促すなどの対応
}

7. 楽観ロックと悲観ロックの注意点とベストプラクティス

7. 楽観ロックと悲観ロックの注意点とベストプラクティス
7. 楽観ロックと悲観ロックの注意点とベストプラクティス

ロックは強力な仕組みですが、使い方を誤るとパフォーマンスの低下やデッドロックの原因になります。

  • 必要な範囲にだけロックを使う
  • リトライ設計を取り入れる
  • デッドロックの可能性を避けるためロック順を統一する
  • 悲観ロック使用時はtimeout設定を忘れずに

特に高トラフィックなWebサービスでは、楽観ロックを基本に設計し、必要な場面だけ悲観ロックを使うのが一般的なベストプラクティスです。

まとめ

まとめ
まとめ

本記事では、Springにおける楽観ロックと悲観ロックの基本概念から、@Version の具体的な使い方、LockModeType を活用した排他制御の手法、さらに例外発生時のリトライ処理や実運用での注意点までを体系的に整理してきました。楽観ロックはデータ更新の衝突が少ない環境で真価を発揮し、Webアプリケーションのように多数のユーザーが並行してデータを扱う場合でも軽量に整合性を保つことができます。一方で、悲観ロックは確実にデータの一貫性を担保したい金融システムや在庫数の更新のようなケースに向いており、トランザクション管理の正確さが求められる場面で力を発揮します。 実装面では、Spring Data JPA が提供する @Version によるバージョン管理や、EntityManager・@Lock アノテーションを通じたロックモード指定など、開発者が意識すべきポイントは多岐にわたります。特に、実運用ではロックの種類を適切に選び、不要な競合やデッドロックを避けながらアプリケーション全体のパフォーマンスと安全性を両立させる設計が欠かせません。 また、ロックの仕組みを理解すると、更新時の例外処理やユーザーへの案内方法、再試行ロジックの導入といった、より実践的な開発にも活かすことができ、日々の業務に直結する知識となるでしょう。ここではより具体的なコード例を交えながら、まとめとしてもう一度整理していきます。

■ ロックを使った整合性確保の基本コード例

下記は、楽観ロックを用いた更新処理のサンプルです。エンティティのバージョンチェックによって、競合があった場合には例外が発生し、整合性を保つ動作が自然に組み込まれています。


@Transactional
public void updateStock(Long id, int newStock) {
    Product product = productRepository.findById(id)
                                      .orElseThrow(() -> new IllegalArgumentException("not found"));

    product.setStock(newStock);

    try {
        productRepository.save(product);
    } catch (ObjectOptimisticLockingFailureException e) {
        // 再読み込みやリトライなどの設計が必要
        throw new RuntimeException("在庫が他のユーザーに更新されました。もう一度お試しください。");
    }
}

悲観ロックを使う場面では、同時に更新される可能性が高い値に対して書き込みロックを取得することで、安全に排他制御を行うことができます。デッドロックや性能劣化に注意しつつ、必要な箇所にのみ適用することが重要です。


@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdForUpdate(@Param("id") Long id);

このように、楽観ロック・悲観ロックの両方を状況に合わせて使い分けることで、Springアプリケーションの安全性とパフォーマンスを高いレベルで両立させることができます。柔軟に適切なロック手法を選択すれば、データ更新の整合性はもちろん、ユーザー体験の向上にもつながります。

先生と生徒の振り返り会話

生徒

「先生、今日の内容で、楽観ロックと悲観ロックの違いがだいぶ理解できました!」

先生

「それはよかったですね。特に、楽観ロックは軽くて扱いやすいので、普段のWebアプリでは頻繁に使うことになるはずですよ。」

生徒

「逆に悲観ロックは、確実に他の更新を防ぎたい時に使うんですよね?」

先生

「その通りです。在庫のように数値が激しく変動する場面や、予約テーブルなどは悲観ロックのほうが安全です。ただしロックによって性能が落ちることもあるので、必要な場面だけうまく使いましょう。」

生徒

「なるほど…。場面に応じて適切なロックを選ぶって、思っていたより奥深いんですね。」

先生

「ええ、ロックの仕組みを理解すると、アプリケーション全体の設計力がぐっと上がりますよ。こうした基本を丁寧に押さえることで、トラブルを未然に防ぐことができます。」

カテゴリの一覧へ
新着記事
JavaのHashSetのaddメソッドを完全ガイド!初心者でもわかるセットの使い方
SpringのBindingResultを完全ガイド!初心者でもわかる入力チェックとエラー処理
Spring Securityでフォームログインを実装!ログイン・ログアウト・Remember-Meの設定方法まとめ
Javaの@Validアノテーションを徹底解説!初心者でもわかる入力値検証の基本
人気記事
No.1
Java&Spring記事人気No1
JavaのExceptionクラスを完全解説!初心者でも理解できる例外処理の基本
No.2
Java&Spring記事人気No2
Spring BootとJavaの互換性一覧!3.5/3.4/3.3はJava 21・17に対応してる?
No.3
Java&Spring記事人気No3
JavaのDateクラスの使い方を完全ガイド!初心者でもわかる日付操作
No.4
Java&Spring記事人気No4
Javaのラムダ式でListを抽出&変換!filterとmapでスマートに操作
No.5
Java&Spring記事人気No5
Javaのラムダ式anyMatchの使い方:containsやList検索の実践テクニック
No.6
Java&Spring記事人気No6
JSPで使えるJavaコードの書き方を徹底解説!初心者向けルールと制限ポイント
No.7
Java&Spring記事人気No7
Spring BootのJakarta移行ガイド!初心者向けjavax→jakarta変更ポイント徹底解説
No.8
Java&Spring記事人気No8
Javaのcountの使い方を完全ガイド!ラムダ式で件数カウントの定石をマスターしよう