Hibernate_ Pessimistic Lock_悲觀鎖

Hibernate_ Pessimistic Lock_悲觀鎖html

http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#transactions-lockingjava

It is not intended(有意的) that users spend much time worrying about locking strategiesIt is usually enough to specify an isolation level for the JDBC connections and then simply let the database do all the work. However, advanced users may wish to obtain exclusive pessimistic locks or re-obtain locks at the start of a new transaction.sql

Hibernate will always use the locking mechanism of the database(使用數據庫的鎖機制); it never lock objects in memory.數據庫

The LockMode class defines the different lock levels that can be acquired by Hibernate. A lock is obtained by the following mechanisms:express

  • LockMode.WRITE is acquired automatically(自動地) when Hibernate updates or inserts a row.api

  • LockMode.UPGRADE can be acquired upon explicit(明確的) user request using SELECT ... FOR UPDATE on databases which support that syntax.緩存

  • LockMode.UPGRADE_NOWAIT can be acquired upon explicit user request using a SELECT ... FOR UPDATE NOWAIT under Oracle.session

  • LockMode.READ is acquired automatically when Hibernate reads data under Repeatable Read or Serializable isolation level. It can be re-acquired by explicit user request.app

  • LockMode.NONE represents the absence(缺席,缺勤) of a lock. All objects switch to(切換到) this lock mode at the end of a Transaction. Objects associated with the session via a call to update() or saveOrUpdate() also start out in this lock mode.less

The "explicit user request" is expressed in one of the following ways:

  • A call to Session.load(), specifying a LockMode.

  • A call to Session.lock().

  • A call to Query.setLockMode().

If Session.load() is called with UPGRADE or UPGRADE_NOWAIT, and the requested object was not yet loaded by the session, the object is loaded using SELECT ... FOR UPDATE. If load() is called for an object that is already loaded with a less restrictive lock than the one requested, Hibernate calls lock() for that object.


Session.lock() performs a version number check if the specified lock mode is READ, UPGRADE or UPGRADE_NOWAIT. In the case of UPGRADE or UPGRADE_NOWAIT, SELECT ... FOR UPDATE is used.


If the requested lock mode is not supported by the database, Hibernate uses an appropriate alternate mode instead of throwing an exception. This ensures that applications are portable.


LockMode枚舉類

public enum LockMode {
   /**
    * No lock required. If an object is requested with this lock
    * mode, a <tt>READ</tt> lock will be obtained if it is
    * necessary to actually read the state from the database,
    * rather than pull it from a cache.<br>
    * <br>
    * This is the "default" lock mode.
    */
   NONE( 0 ),
   /**
    * A shared lock. Objects in this lock mode were read from
    * the database in the current transaction, rather than being
    * pulled from a cache.
    */
   READ( 5 ),
   /**
    * An upgrade lock. Objects loaded in this lock mode are
    * materialized using an SQL <tt>select ... for update</tt>.
    *
    * @deprecated instead use PESSIMISTIC_WRITE
    */
   @Deprecated
   UPGRADE( 10 ),
   /**
    * Attempt to obtain an upgrade lock, using an Oracle-style
    * <tt>select for update nowait</tt>. The semantics of
    * this lock mode, once obtained, are the same as
    * <tt>UPGRADE</tt>.
    */
   UPGRADE_NOWAIT( 10 ),

   /**
    * Attempt to obtain an upgrade lock, using an Oracle-style
    * <tt>select for update skip locked</tt>. The semantics of
    * this lock mode, once obtained, are the same as
    * <tt>UPGRADE</tt>.
    */
   UPGRADE_SKIPLOCKED( 10 ),

   /**
    * A <tt>WRITE</tt> lock is obtained when an object is updated
    * or inserted.   This lock mode is for internal use only and is
    * not a valid mode for <tt>load()</tt> or <tt>lock()</tt> (both
    * of which throw exceptions if WRITE is specified).
    */
   WRITE( 10 ),

   /**
    * Similar to {@link #UPGRADE} except that, for versioned entities,
    * it results in a forced version increment.
    *
    * @deprecated instead use PESSIMISTIC_FORCE_INCREMENT
    */
   @Deprecated
   FORCE( 15 ),

   /**
    *  start of javax.persistence.LockModeType equivalent modes
    */

   /**
    * Optimistically assume that transaction will not experience contention for
    * entities.  The entity version will be verified near the transaction end.
    */
   OPTIMISTIC( 6 ),

   /**
    * Optimistically assume that transaction will not experience contention for
    * entities.  The entity version will be verified and incremented near the transaction end.
    */
   OPTIMISTIC_FORCE_INCREMENT( 7 ),

   /**
    * Implemented as PESSIMISTIC_WRITE.
    * TODO:  introduce separate support for PESSIMISTIC_READ
    */
   PESSIMISTIC_READ( 12 ),

   /**
    * Transaction will obtain a database lock immediately.
    * TODO:  add PESSIMISTIC_WRITE_NOWAIT
    */
   PESSIMISTIC_WRITE( 13 ),

   /**
    * Transaction will immediately increment the entity version.
    */
   PESSIMISTIC_FORCE_INCREMENT( 17 );
   private final int level;

   private LockMode(int level) {
      this.level = level;
   }

   /**
    * Check if this lock mode is more restrictive than the given lock mode.
    *
    * @param mode LockMode to check
    *
    * @return true if this lock mode is more restrictive than given lock mode
    */
   public boolean greaterThan(LockMode mode) {
      return level > mode.level;
   }

   /**
    * Check if this lock mode is less restrictive than the given lock mode.
    *
    * @param mode LockMode to check
    *
    * @return true if this lock mode is less restrictive than given lock mode
    */
   public boolean lessThan(LockMode mode) {
      return level < mode.level;
   }
}


下面針對MySQL對Hibernate 的悲觀鎖作一個測試

LockMode.UPGRADE

can be acquired upon explicit(明確的) user request using SELECT ... FOR UPDATE on databases which support that syntax.

@Before
public void beginTransaction() {
    this.userDao = new UserDao();
    this.roleDao = new RoleDao();
    this.workDao = new WorkDao();
    HibernateUtil.getSessionFactory().getCurrentSession()
            .beginTransaction();
    System.out.println("before=" + HibernateUtil.getSessionFactory()
            .getCurrentSession().hashCode());

}

@Test
public void test892() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Query query = session.createQuery("from Worker as w where id = 1");
    query.setLockMode("w", LockMode.UPGRADE);
    Worker worker = (Worker) query.uniqueResult();
    worker.setAge(10);
    System.out.println(worker.toString());
    System.out.println("commit=" + HibernateUtil.getSessionFactory()
            .getCurrentSession().hashCode());
    HibernateUtil.getSessionFactory().getCurrentSession().getTransaction()
            .commit();
    System.out.println("commit end");
}

其中,query.setLockMode("w", LockMode.UPGRADE);設置鎖的模式爲UPGRADE,運行看結果,以下

before=209429254
Hibernate: select worker0_.id as id1_2_, worker0_.age as age2_2_, worker0_.name as name3_2_, worker0_.version as version4_2_ from tb_worker worker0_ where worker0_.id=1 for update
Worker{id=1, name='Thread-2', age=10, version=34}
commit=209429254
Hibernate: update tb_worker set age=?, name=?, version=? where id=? and version=?
commit end

Process finished with exit code 0

注意其發出的sql語句形式爲select for update,給一個record加上了排它鎖,也就是X鎖,也就是write lock。


LockMode.READ

is acquired automatically when Hibernate reads data under Repeatable Read or Serializable isolation level. It can be re-acquired by explicit user request.

/**
 * LockMode.READ
 */
@Test
public void test8921() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Query query = session.createQuery("from Worker as w where id = 1");
    query.setLockMode("w", LockMode.READ);
    Worker worker = (Worker) query.uniqueResult();
//        worker.setAge(8);
    System.out.println(worker.toString());
    System.out.println("commit=" + HibernateUtil.getSessionFactory()
            .getCurrentSession().hashCode());
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("sleep end");
    HibernateUtil.getSessionFactory().getCurrentSession().getTransaction()
            .commit();
    System.out.println("commit end");
}

LockMode.READ,在api文檔中是這樣說的,

 /**

    * A shared lock. Objects in this lock mode were read from

    * the database in the current transaction, rather than being

    * pulled from a cache.

    */

   READ( 5 ),

運行結果:

before=209429254
Hibernate: select worker0_.id as id1_2_, worker0_.age as age2_2_, worker0_.name as name3_2_, worker0_.version as version4_2_ from tb_worker worker0_ where worker0_.id=1
Worker{id=1, name='s', age=870, version=36}
commit=209429254
sleep end
commit end

發出的sql就是普通的select查詢,不是select ... lock in share mode的形式。因此對於數據庫根本就沒有鎖,只不過是從數據庫讀數據,而不會從Hibernate的緩存中。


使用session.load和session.lock方法

/**
 * LockMode.UPGRADE
 */
@Test
public void test98766() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Worker worker = (Worker) session.load(Worker.class, 1);
    session.lock(worker, LockMode.UPGRADE);
    System.out.println(worker.toString());
    System.out.println("commit=" + HibernateUtil.getSessionFactory()
            .getCurrentSession().hashCode());
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("sleep end");
    HibernateUtil.getSessionFactory().getCurrentSession().getTransaction()
            .commit();
    System.out.println("commit end");
}

    Worker worker = (Worker) session.load(Worker.class, 1);

    session.lock(worker, LockMode.UPGRADE);

這兩個方法就把那一條記錄鎖住了,看一下運行結果,

before=209429254
Hibernate: select worker0_.id as id1_2_0_, worker0_.age as age2_2_0_, worker0_.name as name3_2_0_, worker0_.version as version4_2_0_ from tb_worker worker0_ where worker0_.id=?
Hibernate: select id from tb_worker where id =? and version =? for update
Worker{id=1, name='231', age=870, version=36}
commit=209429254
sleep end
commit end

Process finished with exit code 0

這個結果頗有意思,select id from tb_worker where id =? and version =? for update,是經過這個sql 把那條記錄鎖住的,而不是直接鎖住的,由於是load 方法。。


使用session.buildLockRequest

@Test
public void test766() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Worker worker = (Worker) session.get(Worker.class, 1);
    session.buildLockRequest(new LockOptions(LockMode.UPGRADE)).lock(worker);
    worker.setName("jjjjjjjjjjjjjj");
    System.out.println(session.getCurrentLockMode(worker));
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("sleep end");
    System.out.println("commit=" + HibernateUtil.getSessionFactory()
            .getCurrentSession().hashCode());
    HibernateUtil.getSessionFactory().getCurrentSession().getTransaction()
            .commit();
    System.out.println("commit end");
}

測試結果:

before=209429254
Hibernate: select worker0_.id as id1_2_0_, worker0_.age as age2_2_0_, worker0_.name as name3_2_0_, worker0_.version as version4_2_0_ from tb_worker worker0_ where worker0_.id=?
Hibernate: select id from tb_worker where id =? and version =? for update
UPGRADE
sleep end
commit=209429254
Hibernate: update tb_worker set age=?, name=?, version=? where id=? and version=?
commit end

Process finished with exit code 0

經過結果,lock(obj)是經過select id from tb_worker where id =? and version =? for update 把record鎖住。

以上就是對Hibernate 悲觀鎖的實踐

=====================END=====================

相關文章
相關標籤/搜索