Hibernate_Optimistic Lock_乐观锁java
相对悲观锁而言,乐观锁机制采起了更加宽松的加锁机制。悲观锁大多数状况下依靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。乐观锁机制在必定程度上解决了这个问题。乐观锁,大可能是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增长一个版本标识,在基于数据库表的版本解决方案中,通常是经过为数据库表增长一个"version"字段来实现。数据库
乐观锁的工做原理:读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。session
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
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).
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">
以下,测试方法,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============