默认事务之间会产生数据冲突,所以要求事务在读取数据的时候就对数据加锁,保证数据访问的排它性;
该锁将一直被事务持有,直到事务结束才会释放锁。
一、悲观锁
最常用的,是对查询进行加锁(LockMode.UPGRADE和LockMode.UPGRADE_NOWAIT),悲观锁的可以通过以下三种方式实现:
1)调用Session.load(),并设置了LockMode
2)调用Session.lock()
3)调用Query.setLockMode()
package edu.test;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import edu.po.Users;
import edu.utils.HibernateSessionFactory;
/**
* Title: PessimisticLockTest.java
* Description: 悲观锁测试
* @author yh.zeng
* @date 2017-6-27
*/
public class PessimisticLockTest {
static SessionFactory sessionFactory = HibernateSessionFactory.getSessionFactory();
public static void main(String args[]){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
try {
//方式一,使用Session.load()给数据加悲观锁
Users user = (Users) session.load(Users.class, 6, LockMode.UPGRADE);
System.out.println("用户名:" + user.getUsername() + ",密码:" + user.getPassword());
/*
* //方式二,使用Session.lock()给对象加悲观锁
Users user = (Users) session.load(Users.class, 6);
session.lock(user, LockMode.UPGRADE);
*/
/*
* //方式三,使用Query.setLockMode()给数据加悲观锁
* String hql = "from Users u where u.id = :id";
Query query = session.createQuery(hql);
query.setParameter("id", 6);
query.setLockMode("u", LockMode.UPGRADE);
List<Users> userList = query.list();
for(Users user : userList){
System.out.println("用户名:" + user.getUsername() + ",密码:" + user.getPassword());
}*/
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}finally{
session.close();
}
}
}
hibernate会在生成的SQL后面加上for update子句:
Hibernate: select users0_.id as id0_0_, users0_.version as version0_0_, users0_.username as username0_0_, users0_.password as password0_0_ from test.users users0_ where users0_.id=? for update
用户名:32323,密码:bb
Hibernate提供了2个锁对象,LockMode和LockOptions:
通过LockOptions的源代码,可以发现LockOptions只是LockMode的简单封装(在LockMode的基础上提供了timeout和scope):
......
/**
* NONE represents LockMode.NONE (timeout + scope do not apply)
*/
public static final LockOptions NONE = new LockOptions(LockMode.NONE);
/**
* READ represents LockMode.READ (timeout + scope do not apply)
*/
public static final LockOptions READ = new LockOptions(LockMode.READ);
/**
* UPGRADE represents LockMode.UPGRADE (will wait forever for lock and
* scope of false meaning only entity is locked)
*/
public static final LockOptions UPGRADE = new LockOptions(LockMode.UPGRADE);
public LockOptions(){}
public LockOptions( LockMode lockMode) {
this.lockMode = lockMode;
}
.....
public static final int NO_WAIT = 0;
/**
* Indicates that there is no timeout for the acquisition.
* @see #getTimeOut
*/
public static final int WAIT_FOREVER = -1;
private int timeout = WAIT_FOREVER;
private boolean scope=false;
......
二、乐观锁
乐观锁大多基于数据版本记录机制实现,既为数据增加一个版本标识。
一般,在数据库中增加version列,用来记录每行数据的版本,每次更新对象时,对应行的version字段的值加一。除了使用version作为版本标识,还可以使用timestamp作为版本标识。
CREATE TABLE users (
id int(11) NOT NULL auto_increment,
username varchar(100) default NULL,
password varchar(100) default NULL,
version int(11) default NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Users.java:
package edu.po;
/**
* Users entity. @author MyEclipse Persistence Tools
*/
public class Users implements java.io.Serializable {
// Fields
private Integer id;
private Integer version;
private String username;
private String password;
// Constructors
/** default constructor */
public Users() {
}
/** full constructor */
public Users(String username, String password) {
this.username = username;
this.password = password;
}
// Property accessors
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getVersion() {
return this.version;
}
public void setVersion(Integer version) {
this.version = version;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
Users.hbm.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="edu.po.Users" table="users" catalog="test">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native" />
</id>
<version name="version" type="java.lang.Integer">
<column name="version" />
</version>
<property name="username" type="java.lang.String">
<column name="username" length="100" />
</property>
<property name="password" type="java.lang.String">
<column name="password" length="100" />
</property>
</class>
</hibernate-mapping>
乐观锁测试demo:
package edu.test;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import edu.po.Users;
import edu.utils.HibernateSessionFactory;
/**
* Title: OptimisticTest.java
* Description: Hibernate乐观锁测试
* @author yh.zeng
* @date 2017-6-27
*/
public class OptimisticTest {
static SessionFactory sessionFactory = HibernateSessionFactory.getSessionFactory();
public static void main(String args[]) {
Session session1 = sessionFactory.openSession();
Session session2 = sessionFactory.openSession();
try {
Users user1 = (Users)session1.get(Users.class, 6);
Users user2 = (Users)session2.get(Users.class, 6);
System.out.println("user1的version="+user1.getVersion()+","+"user2的version="+user2.getVersion());
Transaction transaction1 = session1.beginTransaction();
Transaction transaction2 = session2.beginTransaction();
user1.setPassword("11");
transaction1.commit();
System.out.println("user1的version="+user1.getVersion()+","+"user2的version="+user2.getVersion());
user2.setPassword("111111");
transaction2.commit();
} catch (Exception e) {
e.printStackTrace();
}finally{
session1.close();
session2.close();
}
}
}
运行结果如下,可以看到由于transaction1提交时,version字段已经被修改,transaction2提交时会抛出org.hibernate.StaleObjectStateException异常:
Hibernate: select users0_.id as id0_0_, users0_.version as version0_0_, users0_.username as username0_0_, users0_.password as password0_0_ from test.users users0_ where users0_.id=?
Hibernate: select users0_.id as id0_0_, users0_.version as version0_0_, users0_.username as username0_0_, users0_.password as password0_0_ from test.users users0_ where users0_.id=?
user1的version=17,user2的version=17
Hibernate: update test.users set version=?, username=?, password=? where id=? and version=?
user1的version=18,user2的version=17
Hibernate: update test.users set version=?, username=?, password=? where id=? and version=?
2017-7-3 21:59:03 org.hibernate.event.def.AbstractFlushingEventListener performExecutions
严重: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [edu.po.Users#6]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1782)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2425)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2325)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2625)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:366)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at edu.test.OptimisticTest.main(OptimisticTest.java:42)
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [edu.po.Users#6]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1782)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2425)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2325)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2625)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:366)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at edu.test.OptimisticTest.main(OptimisticTest.java:42)