Hibernate_Optimistic Lock_樂觀鎖

Hibernate_Optimistic Lock_樂觀鎖java

相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本(Version)記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個"version"字段來實現。數據庫

樂觀鎖的工做原理:讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。session


Optimistic locking properties (optional)

When using long transactions or conversations that span several database transactions, it is useful to store versioning data to ensure that if the same entity is updated by two conversations, the last to commit changes will be informed and not override the other conversation's work. It guarantees some isolation while still allowing for good scalability and works particularly well in read-often write-sometimes situations.app


You can use two approaches: a dedicated version number or a timestamp.less


A version or timestamp property should never be null for a detached instance. Hibernate will detect any instance with a null version or timestamp as transient, irrespective of what other unsaved-value strategies are specified. Declaring a nullable version or timestamp property is an easy way to avoid problems with transitive reattachment in Hibernate. It is especially useful for people using assigned identifiers or composite keys.ide


Version number

You can add optimistic locking capability to an entity using the @Version annotation:性能

@Entity
public class Flight implements Serializable {
...
    @Version
    @Column(name="OPTLOCK")
    public Integer getVersion() { ... }
}

The version property will be mapped to the OPTLOCK column, and the entity manager will use it to detect conflicting updates (preventing lost updates you might otherwise see with the last-commit-wins strategy).測試


The version column may be a numeric. Hibernate supports any kind of type provided that you define and implement the appropriate UserVersionType.ui


The application must not alter the version number set up by Hibernate in any way. To artificially increase the version number, check in Hibernate Entity Manager's reference documentation LockModeType.OPTIMISTIC_FORCE_INCREMENT or LockModeType.PESSIMISTIC_FORCE_INCREMENT.spa


If the version number is generated by the database (via a trigger for example), make sure to use @org.hibernate.annotations.Generated(GenerationTime.ALWAYS).


Timestamp

Alternatively, you can use a timestamp. Timestamps are a less safe implementation of optimistic locking. However, sometimes an application might use the timestamps in other ways as well.


Simply mark a property of type Date or Calendar as @Version.

@Entity
public class Flight implements Serializable {
...
    @Version
    public Date getLastUpdate() { ... }
}

When using timestamp versioning you can tell Hibernate where to retrieve the timestamp value from - database or JVM - by optionally adding the @org.hibernate.annotations.Source annotation to the property. Possible values for the value attribute of the annotation are org.hibernate.annotations.SourceType.VM and org.hibernate.annotations.SourceType.DB. The default is SourceType.DB which is also used in case there is no @Source annotation at all.


Like in the case of version numbers, the timestamp can also be generated by the database instead of Hibernate. To do that, use @org.hibernate.annotations.Generated(GenerationTime.ALWAYS).

Note

<Timestamp> is equivalent to <version type="timestamp">. And <timestamp source="db"> is equivalent to <version type="dbtimestamp">


使用Version number驗證樂觀鎖

以下,測試方法,AppMain1.java

package com.test;

import com.lyx.HibernateUtil;
import com.lyx.WorkDao;
import com.lyx.Worker;
import org.hibernate.Session;

/**
 * Created by Lenovo on 2014/12/2.
 */
public class AppMain1 {

    public static void main(String args[]) {
        final WorkDao workDao = new WorkDao();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Session session1 = HibernateUtil.getSessionFactory().getCurrentSession();
                System.out.println("current session1 hashcode=" + HibernateUtil.getSessionFactory()
                        .getCurrentSession().hashCode());
                session1.getTransaction().begin();
                session1.getTransaction().setTimeout(Integer.MAX_VALUE);
                Worker worker1 = workDao.get(1);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 sleep end");
                System.out.println("session1 version=" + worker1.getVersion());
                worker1.setName(Thread.currentThread().getName());
                System.out.println("session1 after setName version=" + worker1.getVersion());
//                workDao.update(worker1);
                System.out.println("session1 update");
                session1.getTransaction().commit();
                System.out.println("session1 commit");
                System.out.println("session1 after commit version=" + worker1.getVersion());
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
                System.out.println("current session2 hashcode=" + HibernateUtil.getSessionFactory()
                        .getCurrentSession().hashCode());
                session2.getTransaction().begin();
                Worker worker2 = workDao.get(1);
                try {
                    Thread.sleep(8000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 sleep end");
                System.out.println("session2 version=" + worker2.getVersion());
                worker2.setName(Thread.currentThread().getName());
                System.out.println("session2 after setName version=" + worker2.getVersion());
//                workDao.update(worker2);
                System.out.println("session2 update");
                session2.getTransaction().commit();
                System.out.println("session2 commit");
                System.out.println("session2 after commit version=" + worker2.getVersion());
            }
        });

        t1.start();
        t2.start();
    }
}

運行結果:

current session1 hashcode=1665124518
current session hashcode=1665124518
current session2 hashcode=1977733903
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=?
current session hashcode=1977733903
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=?
t1 sleep end
session1 version=31
session1 after setName version=31
session1 update
Hibernate: update tb_worker set age=?, name=?, version=? where id=? and version=?
session1 commit
session1 after commit version=32
t2 sleep end
session2 version=31
session2 after setName version=31
session2 update
Hibernate: update tb_worker set age=?, name=?, version=? where id=? and version=?
Exception in thread "Thread-2" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.lyx.Worker#1]
	at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
	at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
	at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
	at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
	at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
	at com.test.AppMain1$2.run(AppMain1.java:61)
	at java.lang.Thread.run(Thread.java:745)

最終拋出了StaleObjectStateException異常,就是@Version的做用,做了一個版本檢查。

StaleObjectStateException

好的,以上就是經過@Version實現了樂觀鎖,並進行了驗證。

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

相關文章
相關標籤/搜索