Lockのメモ


JPAでは、EntityManager.lock(Object, LockModeType) でLockを取得できるらしい。
LockModeTypeは、WRITEとREADが用意されている。

とりあえず、次のようなコードでロックの動作を確認をしてみる。

package sample;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.LockModeType;
import javax.persistence.Persistence;

public class LockExample {
	
	public static void main(String[] args) {
	    int id = -1;
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hibernate");
		EntityManager em = emf.createEntityManager();
		
	    final EntityTransaction tx = em.getTransaction();
	    tx.begin();

	    Actor brad = new Actor("Brad Pitt");
	    em.persist(brad);
	    id = brad.getId();
	    
		tx.commit();
		em.close();
		
	    System.out.println("################################################ ");
		
		Thread t1 = new Thread(new Updater(emf, "Johnny Depp", id, 3));
		Thread t2 = new Thread(new Updater(emf, "Orlando Bloom", id, 0));
		
		
		t1.start();
		
		// 1秒Wait
	    try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		t2.start();
		
		/*
		 * Lockされているなら、次の順で更新されるはず。
		 * 「Brad Pitt」→「Johnny Depp」→「Orlando Bloom」
		 */ 
		
		try {
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		emf.close();
	    System.out.println("################################################ ");
	}
	
	private static class Updater implements Runnable {
		private EntityManager em;
		private String name;
		private int actorId;
		private int minuts;
		public Updater(EntityManagerFactory emf, String name, int actorId, int minuts) {
			this.em = emf.createEntityManager();
			this.name = name;
			this.actorId = actorId;
			this.minuts = minuts;
		}
		public void run() {
		    final EntityTransaction tx = em.getTransaction();
		    tx.begin();
		    
		    Actor actor = em.find(Actor.class, this.actorId);
		    System.out.println("############# find: " + name);
		    em.lock(actor, LockModeType.WRITE);
		    System.out.println("############# lock: " + name);

		    try {
				Thread.sleep(1000 * minuts);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		    System.out.println("############# before update: " + name);
		    actor.setName(name);
		    
			tx.commit();
		    System.out.println("############# commit: " + name);
		}
		@Override
		public void finalize() {
			em.close();
			em = null;
		}
	}

}

Lockを取得すれば、「Brad Pitt」→「Johnny Depp」→「Orlando Bloom」という順に更新されるはず。

いざ、実行・・・・してみるといきなりNullPointerException発生。
どうやらEntityManager.lock()でthrowされているらしい・・・

悩むこと数十分。。

どうもLockModeType.WRITEを使用する場合は@Versionが必要らしい。


で、実行結果

Hibernate: 
    insert 
    into
        Actor
        (id, name, version) 
    values
        (null, ?, ?)
################################################ 
Hibernate: 
    select
        actor0_.id as id0_0_,
        actor0_.name as name0_0_,
        actor0_.version as version0_0_ 
    from
        Actor actor0_ 
    where
        actor0_.id=?
############# find: Johnny Depp
Hibernate: 
    update
        Actor 
    set
        version=? 
    where
        id=? 
        and version=?
############# lock: Johnny Depp
Hibernate: 
    select
        actor0_.id as id0_0_,
        actor0_.name as name0_0_,
        actor0_.version as version0_0_ 
    from
        Actor actor0_ 
    where
        actor0_.id=?
############# before update: Johnny Depp
Hibernate: 
    update
        Actor 
    set
        name=?,
        version=? 
    where
        id=? 
        and version=?
############# commit: Johnny Depp
############# find: Orlando Bloom
Hibernate: 
    update
        Actor 
    set
        version=? 
    where
        id=? 
        and version=?
############# lock: Orlando Bloom
############# before update: Orlando Bloom
Hibernate: 
    update
        Actor 
    set
        name=?,
        version=? 
    where
        id=? 
        and version=?
############# commit: Orlando Bloom
################################################ 

SELECT * FROM ACTOR;

ID NAME VERSION
41 Orlando Bloom 4

期待通り「Orlando Bloom」で更新されているけど、無駄にversionを更新しているし、FOR UPDATE してくれていない。

悩むこと数十分。。

LockModeType.WRITE は、バージョンをインクリメントするものでFOR UPDATEするものではないらしい。
やはり、ドキュメントはちゃんとみないと駄目かORZ

というわけで、 LockModeType.WRITE を LockModeType.READ に変更して実行。

Hibernate: 
    insert 
    into
        Actor
        (id, name, version) 
    values
        (null, ?, ?)
################################################ 
Hibernate: 
    select
        actor0_.id as id0_0_,
        actor0_.name as name0_0_,
        actor0_.version as version0_0_ 
    from
        Actor actor0_ 
    where
        actor0_.id=?
############# find: Johnny Depp
Hibernate: 
    select
        id 
    from
        Actor 
    where
        id =? 
        and version =? for update
            
############# lock: Johnny Depp
Hibernate: 
    select
        actor0_.id as id0_0_,
        actor0_.name as name0_0_,
        actor0_.version as version0_0_ 
    from
        Actor actor0_ 
    where
        actor0_.id=?
############# before update: Johnny Depp
Hibernate: 
    update
        Actor 
    set
        name=?,
        version=? 
    where
        id=? 
        and version=?
############# commit: Johnny Depp
############# find: Orlando Bloom
Hibernate: 
    select
        id 
    from
        Actor 
    where
        id =? 
        and version =? for update
            
############# lock: Orlando Bloom
############# before update: Orlando Bloom
Hibernate: 
    update
        Actor 
    set
        name=?,
        version=? 
    where
        id=? 
        and version=?
############# commit: Orlando Bloom
################################################ 

SELECT * FROM ACTOR;

ID NAME VERSION
42 Orlando Bloom 2

今度は、 FOR UPDATE してくれているみたい。

そ〜いうもんなんだということで終了。。

と思ったけど、試しにEntityManager.lock()の呼び出しをコメントアウトしたらデッドロックが発生した。。

え〜?とまた悩むこと数十分。。

どうもは、H2(使用しているRDB)ではSELECTで共有ロックを取得することが関係しているみたい。
で、排他ロックは共有ロックが開放されるまでWAITするらしい。

つまり、次のようなことが起こっているのかな?多分。。

①Johnny がSELECTし、共有ロック取得
②Orlando がSELECTし、共有ロック取得
③Orlando がUPDATE。排他ロックを取得したいが、Johnny の共有ロックの開放待ち
④Johnny がUPDATE。排他ロックを取得したいが、Orlando の共有ロックの開放待ち
デッドロック・・・

DB2でも似たような現象に遭遇したような気がするなぁ。

もう疲れたので今度こそ終了。。