カテゴリ: Spring 更新日: 2026/04/04

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

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

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

生徒

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

先生

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

生徒

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

先生

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

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

「1. 楽観ロックとは?@Versionを使った競合防止」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。

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

楽観ロック(オプティミスティック・ロック)とは、データの更新時に「おそらく他の人は同時に更新しないだろう」と前向きに考えて処理を進める仕組みです。データベースのテーブルを長時間占有(ロック)しないため、システムの処理速度を落としにくいのが大きなメリットです。

Spring Data JPAでは、エンティティのフィールドに@Versionアノテーションを付与するだけで、この高度な排他制御を自動化できます。具体的には、データ取得時のバージョン番号と更新実行時のバージョン番号を比較し、もし数値が異なれば「誰かが先に更新した」と判断してエラーを発生させます。

未経験者向けのイメージ例:ホテルの予約

あなたがWebサイトでホテルの空室を「1部屋」確認したとします。申し込みボタンを押す直前に、別の人がその最後の1部屋を予約してしまいました。このとき、古い情報のまま予約を完了させず、「情報が更新されました。もう一度最初からやり直してください」と安全に止めてくれるのが楽観ロックの役割です。


@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private int stock;

    // このアノテーションが重要!
    @Version
    private Long version;

    // getter/setterは省略
}

上記のコードのように、versionという項目を追加して@Versionを付けるだけです。これにより、データが更新されるたびに数値が 1, 2, 3... と自動でカウントアップされます。

もし同時に二人が同じデータを編集しようとしても、後から保存ボタンを押した人には例外(OptimisticLockingFailureException)が投げられ、不正な上書き(いわゆる「ロストアップデート」)を未然に防ぐことができます。ユーザーには「他のユーザーが更新したため、再度読み込み直してください」といった案内を出すのが一般的です。

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

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

Spring Bootで楽観ロックがどのように動作するのか、その仕組みを具体的かつシンプルに紐解いていきましょう。基本的には、データの「バージョン番号」をチェックすることで、後から割り込んできた更新を防ぐ仕組みです。

処理の3ステップ
  1. 取得:DBからデータを読み込む際、現在のバージョン番号(例:1)も一緒に取得します。
  2. 変更:プログラム内でデータを書き換えます。この間、DBのデータはロックされません。
  3. 検証:保存の直前、DBの番号が「1」のままか確認します。1であれば「2」に更新して保存完了。もし誰かが先に更新して「2」になっていれば、エラーを投げて更新を中止します。

プログラミングに慣れていない方のために、身近な「共有ドキュメントの編集」を例にしたサンプルプログラムを見てみましょう。


// サービス層での処理イメージ
@Service
public class DocumentService {
    
    @Transactional
    public void updateContent(Long id, String newContent) {
        // 1. 編集前のデータを取得(この時、内部でversion番号も保持される)
        Document doc = repository.findById(id).orElseThrow();
        
        // 2. 内容を書き換える
        doc.setContent(newContent);
        
        // 3. 保存実行(Springが自動でDBのversionと比較し、不一致なら例外を出す)
        repository.save(doc);
    }
}

もし、あなたが「保存ボタン」を押す数秒前に、同僚が同じドキュメントを更新していたらどうなるでしょうか?その場合、SpringはOptimisticLockingFailureExceptionという例外を発生させます。

このエラーは「システム故障」ではなく、データの整合性を守ったという「安全装置の作動」です。開発者はこの例外をキャッチし、ユーザーに「他の人が更新しました。最新の状態を確認してから、もう一度入力してください」といったメッセージを表示する親切な設計を行うことが重要です。

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. 楽観ロックと悲観ロックの違いと使い分け
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. 楽観ロックと悲観ロックの注意点とベストプラクティス
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アプリでは頻繁に使うことになるはずですよ。」

生徒

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

先生

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

生徒

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

先生

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

この記事を読んだ人からの質問

この記事を読んだ人からの質問
この記事を読んだ人からの質問

プログラミング初心者からのよくある疑問/質問を解決します

Springで楽観ロックとは何ですか?@Versionはどのような意味ですか?

楽観ロックは同時更新されない前提で処理し、更新時にバージョン番号が一致しない場合に例外を発生させて整合性を保つ仕組みです。Springでは@Versionアノテーションを使うことで自動的にバージョンチェックが行われます。

Springの悲観ロックはどんなときに使うべきですか?

悲観ロックは同時に他の人が更新する可能性が高い状況、たとえば在庫数の操作や予約システムなど、確実に排他制御が必要な場面で使用されます。
カテゴリの一覧へ
新着記事
New1
Servlet
JavaのGenericServletクラスのgetInitParameterNamesメソッドを徹底解説!初心者でもわかる初期化パラメータの取得方法
更新記事
New2
JSP
JSPとは何か?初心者向けにできること・仕組み・特徴をやさしく解説
更新記事
New3
Servlet
JavaのHttpServletRequestクラスとgetRemoteAddrメソッドを初心者向けに徹底解説!
更新記事
New4
Thymeleaf
Thymeleafのth:srcの使い方を完全ガイド!初心者でもわかる画像やリソース設定
更新記事
人気記事
No.1
Java&Spring記事人気No1
Spring
Spring BootとJavaの互換性一覧!3.5/3.4/3.3はJava 21・17に対応してる?
No.2
Java&Spring記事人気No2
Spring
Springの@Serviceアノテーションの使い方を徹底解説!初心者でもわかるSpring フレームワーク入門
No.3
Java&Spring記事人気No3
Servlet
JavaのHttpSessionを徹底解説!初心者でもわかるセッション管理の基本
No.4
Java&Spring記事人気No4
Java
Java開発環境「Eclipse(Pleiades)」のインストール方法とメリットを初心者向けに解説
No.5
Java&Spring記事人気No5
JSP
JSPの基本タグ一覧と使い方まとめ!実務で使えるタグを紹介
No.6
Java&Spring記事人気No6
Spring
Spring BootとVS Codeで開発を始めよう!拡張機能・launch.json・ホットリロードを丁寧に解説
No.7
Java&Spring記事人気No7
Spring
Springの@Componentアノテーションの使い方を徹底解説!初心者でもわかるSpring Boot入門
No.8
Java&Spring記事人気No8
Java
Javaの@SuppressWarningsアノテーションの使い方を完全ガイド!初心者でもわかる警告の抑制方法