Hibernate_Optimistic Lock_乐观锁

Hibernate_Optimistic Lock_乐观锁


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.


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


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.


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.


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.


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===========================

你可能感兴趣的:(Hibernate_Optimistic Lock_乐观锁)