更新処理の流れ
- EntityManagerを取得する。(検索処理参照)
- トランザクション開始
- 更新処理(追加、修正、削除)
- コミット(トランザクション終了)
- EntityManagerをクローズする
実際のコードはこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
EntityManager em = getEntityManager(); em.getTransaction().begin(); SampleEntity entity = new SampleEntity(); entity.setKey("0"); entity.setVal("0"); entity.setRevision("0"); em.merge(entity); em.getTransaction().commit(); em.close(); |
EntityManager#merge(Object) で、追加・更新を行う。DB上に、対象のエンティティと同一のキーをもつレコードが存在していればUPDATE、なければINSERTをしてくれるので便利。
削除の場合は一旦mergeしてからEntityManager#remove(Object)を実行する。
楽観的ロック
DB更新時の排他処理について。
レコード毎にバージョン番号を持ち、まず最初に対象となるデータを取得しておき、更新時にDB上の対象データのバージョン番号に変化が無いか調べ、変化があれば「自分より前に、自分以外の誰かが更新した=このまま登録しては他人の変更を上書きしてしまうのでダメ」、変化がなければ「誰にも更新されていない=そのまま更新してよい=その一瞬だけロックして書き込んですぐに開放する」と判断するというのが楽観的ロック。CVS、SVNなどのリビジョン番号での管理がこれに当たる。
この楽観的ロックに関する機能がJPAには備わっているというので、使ってみた。方法は簡単、エンティティでバージョン番号を意味する列に「@Version
」アノテーションを付加するだけ!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Entity @Table(name="sample_entity") public class SampleEntity implements Serializable { @Id @Column(unique=true, nullable=false, length=100) private String key; @Column(nullable=false, length=100) private String val; @Version @Column(nullable=false) private long revision; … |
このようなエンティティクラスを使用して更新処理を行った場合、エラーの無い場合(新規登録時含む)はそのまま処理が進み、排他エラーになった場合はOptimisticLockExceptionが発生する。
悲観的ロック
俺はあまり好きではないので使わない悲観的ロックについて。
操作開始から終了までずっとロックしっぱなしにして、その間は他からの更新を許さないのが悲観的ロック。この場合、エンティティクラスに@Versionアノテーションなどは不要(別にあってもいいんだろうけど)。
これまた簡単にできるようだ。ロックをかける方法は主に以下の二つ。
- 検索時にロック
- 任意のタイミングでロック
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 検索時にロックする場合・主キー検索 SampleEntity entity1 = em.find(SampleEntity.class, "abc", LockModeType.PESSIMISTIC_WRITE); // 検索時にロックする場合・JPQLでの複数件検索 List<SampleEntity> entities = em.createQuery("select s from SampleEntity s") .setLockMode(LockModeType.PESSIMISTIC_WRITE) .getResultList(); // 任意のタイミングでロックする場合 SampleEntity entity2 = em.find(SampleEntity.class, "abc"); em.lock(entity2, LockModeType.PESSIMISTIC_WRITE); |
確かに簡単。