LoginSignup
10
15

More than 5 years have passed since last update.

ロングトランザクションにおける楽観ロック(Spring+JPA)

Posted at

JPAに楽観ロックがサポートされているのでそれを使おうとしたけれど、ロングトランザクションの場合、どうやるのか不明でここ3日ほど悩みました。
※ロングトランザクションという言葉もこの調査で初めて知りましたw

やりたかったのは以下のようなこと。

①ID入力→検索ボタン押下→DBからデータ取得→画面にデータ表示
②画面入力でデータを書き換える
③更新ボタン押下→DBのデータ更新

この①~③の処理をひとつのトランザクションでやるようなイメージで①~③の間に他の誰かがデータ更新していたら異常としたい。

というわけで、今回はロングトランザクション(複数リクエスト)時の楽観ロック方法
まずは楽観ロックするためのバージョニング用カラムを追加してEntityにも対応するプロパティと@Versionアノテーションを設定

NameT.java
package jp.test.spring.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name = "NAMET")
public class NameT implements Serializable {

    @Id
    @Column(nullable = false)
    private int id;

    @Column
    private String name;

    @Version                // ←@Versionアノテーション追加
    private int version;    // ←バージョニング用のカラムを追加

    // getter/setter
}

@Transactionalアノテーションが設定されたDB更新処理関数でEntityManager.lock()を使って楽観or悲観ロックを取得。
※第2引数に渡すパラメータでロック種別が設定可能

TestServiceImpl.java
@Service("testService")
public class TestServiceImpl implements TestService {

    @Transactional
    @Override
    public void updateNameT(NameT nameT) {
        NameT updateNameT = entityManager.find(NameT.class, nameT.getId());
        entityManager.lock(updateNameT, LockModeType.OPTIMISTIC);
        updateNameT.setName(nameT.getName());

        entityManager.merge(updateNameT);
    }
}

けど、これだと一つのリクエスト内でしかトランザクション張れないので、ロングトランザクションの場合どうやるの?ってずっと悩んでました。。。

ロック取得前に手動でバージョン情報を確認するしかないのでは?と思っていろいろ調べたらTERASOLUNAさんのサイト(https://terasolunaorg.github.io/guideline/public_review/ArchitectureInDetail/ExclusionControl.html#id12 )にそのような記載がありました。
手動で確認してorg.springframework.orm.ObjectOptimisticLockingFailureExceptionをthrowすればいいみたいです。
この場合、関数にthrows ObjectOptimisticLockingFailureExceptionをつけなくてもOKでした。

というわけで、修正したソースが以下になります。
ObjectOptimisticLockingFailureExceptionコンストラクタの第1引数にEntityのClass<?>、第2引数にEntityのPrimaryKeyのObjectを設定するみたいです。

TestServiceImpl(修正後).java
@Service("testService")
public class TestServiceImpl implements TestService {

    @Transactional
    @Override
    public void update(NameT nameT) {
        NameT updateNameT = entityManager.find(NameT.class, nameT.getId());
        if (updateNameT.getVersion() != nameT.getVersion()) {
            throw new ObjectOptimisticLockingFailureException(NameT.class, nameT.getId());
        }

        entityManager.lock(updateNameT, LockModeType.OPTIMISTIC);
        updateNameT.setName(nameT.getName());

        entityManager.merge(updateNameT);
    }
}

update()に渡すNameTは検索ボタン押下でデータ取得したときのバージョン情報が格納されたものである必要あります。
更新時にバージョン情報が違うとObjectOptimisticLockingFailureExceptionが発生してデータ更新されません。しかもロールバックが実行されるぽい。

やり方がわかったので今回のプロジェクトは楽観ロックで実装できそうです。
めでたしめでたし

10
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
15