Hibernate悲观锁/乐观锁

本文介绍了Hibernate中的悲观锁和乐观锁。悲观锁在读取数据时即加锁,直至事务结束才释放,通常通过Session.load()、Session.lock()或Query.setLockMode()实现,加锁方式如SQL的for update子句。乐观锁则基于数据版本记录,如增加version列,每次更新时版本号递增。在实际操作中,当两个事务同时尝试更新同一数据时,未通过版本检查的事务会被抛出StaleObjectStateException异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

默认事务之间会产生数据冲突,所以要求事务在读取数据的时候就对数据加锁,保证数据访问的排它性;
该锁将一直被事务持有,直到事务结束才会释放锁。

一、悲观锁

最常用的,是对查询进行加锁(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)


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值