悲观锁

为了避免读写数据不及时,导致脏数据产生的问题,hibernate引入了悲观锁和乐观锁。


下面先介绍悲观锁


悲观锁


新建一个java项目,结构如图:




实体类User代码:


package com.robert.pojo;

public class User {

	private int id ;
	private String name ;
	private String pwd ;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPwd() {
		return pwd;
	}
	public void setPwd(String pwd) {
		this.pwd = pwd;
	}
	
	
}


User.hbm.xml代码:


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!-- This mapping demonstrates content-based discrimination for the table-per-hierarchy 
	mapping strategy, using a formula discriminator. -->

<!-- package:声明pojo类所在的包,如果不写,那么在class中的name里需要指明pojo类所在的包;
     schema:指数据库的模式,一个模式下可以有多张表。
 -->
<hibernate-mapping package="com.robert.pojo">

	<!-- class:指映射一个pojo类,
	         1)提供了公共的无参构造方法,通过反射产生对象。
	         2)属性用private修饰,并且生成对应的set/get方法。
	         3)类不能用final来修饰,hibernate会产生代理类(通过cglib)。
	     name:表示pojo类名;
	     table:表示pojo类对应的数据库中的表名;如果不写,默认是类名。
	 -->
	<class name="User" table="user">
        <!-- 
        	id:表示实体类的标识(OID),对应数据库表的主键;
        	name:指实体类的标识属性名;
        	column:表示对应数据库表的列名,如果不写,则和属性名一致;
        	length:表示数据库表中对应数据类型的长度,如果不写,有默认长度;
        	type:表示类型,如果不写,hibernate会找对应pojo类的属性类型;
         -->
		<id name="id" column="id">
			<!-- 主键生成策略
				 increment:用于为long,short或者int类型生成唯一标识,
				                            只有在没有其他进程往同一张表中插入数据时才能使用。在集群下不要使用。
				                            (可以使用的数据库是:mysql,ms sql)。
				 identity:对DB2,MySQL,MS SQL Server,Sybase和HypersonicSQL的内置标识字段提供,
				                          返回的标识符是long, short, 或者int类型的。
				 sequence:在支持序列的数据库中使用,使用数据库是oracle。 
				                           例子:
				          <generator class="sequence" >
				                                         注释:param中的值是数据库sequence的名称 
				                <param name="sequence">user_seq</param>
			              </generator>
				 uuid:	UUID被编码为一个32位的16进制数字的字符串;
				 native:根据底层的数据库能力选择,identity,sequence或者hilo中的一个;
				 assigned:自己指定主键。
			 -->
			<generator class="native" />
		</id>
		
		<!-- 
			实体类的属性
			name:指明pojo类属性名称(区分大小写);
			column:实体类属性对应的数据库表列名;
		 -->
		<property name="name" >
			<column name="name"></column>
		</property>
		<property name="pwd" />

	</class>

</hibernate-mapping>

HIbernateUtil代码

package com.robert.util;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

	private static Configuration cfg = null;
	private static SessionFactory factory = null;
	private static Session session = null ;
	
	static {
		init();
	}

	public static void init() {
		cfg = new Configuration().configure();
		factory = cfg.buildSessionFactory(new StandardServiceRegistryBuilder()
				.applySettings(cfg.getProperties()).build());
	}

	public static Session getSession() {
		if (factory != null){
			session = factory.openSession();
			return session ;
		}
		

		init();
		session = factory.openSession();
		return session;
	}
	
	public static void closeSession() {
		if(session!=null && session.isOpen())
			session.close();
		
	}

}


hibernate.cfg.xml代码:


<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
	<session-factory>
		<!-- 配置数据库连接信息 -->
		<property name="connection.driver_class">
			com.mysql.jdbc.Driver
		</property>
		<property name="connection.url">jdbc:mysql:///hibernate4</property>
		<property name="connection.username">root</property>
		<property name="connection.password">root</property>
		<!-- 数据库方言 -->
		<property name="hibernate.dialect">
			org.hibernate.dialect.MySQL5Dialect
		</property>
		<!-- 是否打印sql语句 -->
		<property name="show_sql">true</property>
		<!-- 格式化sql语句 -->
		<property name="format_sql">true</property>
		<!-- 数据库更新方式: 
		 1、create:每次更新都先把原有数据库表删除,然后创建该表;
		 2、create-drop:使用create-drop时,在显示关闭SessionFacroty时(sessionFactory.close()),将drop掉数据库Schema(表) 
		 3、validate:检测;
		 4、update(常用):如果表不存在则创建,如果存在就不创建
		 -->
		<property name="hbm2ddl.auto">update</property>

		<!-- 加载User实体类对应的配置文件 -->
		<mapping resource="com/robert/pojo/User.hbm.xml" />

	</session-factory>
</hibernate-configuration>

HibernateTest测试类代码:


package com.robert.test;

import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.metamodel.domain.Hierarchical;
import org.junit.Test;

import com.robert.pojo.User;
import com.robert.util.HibernateUtil;

public class HibernateTest {

	/**
	 * session的save方法,保存数据 session中状态改变:瞬时-->持久-->游离
	 */
	@Test
	public void testSave() {

		Session session = null;
		Transaction tx = null;
		User user = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();

			// 构造对象--瞬时状态
			user = new User();
			user.setName("小明2");
			user.setPwd("22221");

			// 持久状态,user被session管理,并且id有值--oid
			session.save(user);
			// 在持久化状态下:hibernate会执行脏数据检查(之前的数据成为脏数据)
			// 当提交事务,或清理缓存时,发现session中数据和之前要放入数据库中数据(此时数据仍然在session中,并未真正在数据库中)不一致时,
			// 将会把session中的数据更新到数据库中。
			user.setName("小友");

			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
			throw new HibernateException(e.getCause());
		} finally {
			HibernateUtil.closeSession();
		}
		// user处于游离状态,但是在内存中仍然存在
		System.out.println("name=" + user.getName());
	}

	/**
	 * 测试悲观锁
	 */
	@Test
	public void testGetPessimistic() {
		Session session = null;
		Transaction tx = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();

			User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
			System.out.println("name=" + user.getName());
			user.setName("张三") ;
			session.update(user) ;
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
		} finally {
			HibernateUtil.closeSession();
		}
	}
	
	
	/**
	 * 测试悲观锁
	 */
	@Test
	public void testGetPessimistic2() {
		Session session = null;
		Transaction tx = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();
			
			//LockOptions设置锁定策略
			User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
			System.out.println("name=" + user.getName());
			user.setName("张三") ;
			session.update(user) ;
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
		} finally {
			HibernateUtil.closeSession();
		}
	}
	

	
}


悲观锁书写方式:


	/**
	 * 测试悲观锁
	 */
	@Test
	public void testGetPessimitic() {
		Session session = null;
		Transaction tx = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();
			
			//LockOptions设置锁定策略
			User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
			System.out.println("name=" + user.getName());
			user.setName("张三") ;
			session.update(user) ;
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
		} finally {
			HibernateUtil.closeSession();
		}
	}
	


悲观锁一共有一下几种形式:




上面的测试代码中有两个测试悲观锁的方法,有两个session,如果同时启动,就相当于同时有两个线程启动,当第一个session执行了

			User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;

这句话后,后面的线程就需要等待,直到这个session关闭之后,其他的线程才能继续执行,以此类推。



乐观锁:


乐观锁和悲观锁不同的是,乐观锁不是排他性,而是使用了一个版本version来记录,当数据被更改后,version会更改。



下面新建一个java项目,结构如下:




实体类User代码:


package com.robert.pojo;

public class User {

	private int id ;
	private String name ;
	private String pwd ;
	private int version ;
	
	public int getVersion() {
		return version;
	}
	public void setVersion(int version) {
		this.version = version;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPwd() {
		return pwd;
	}
	public void setPwd(String pwd) {
		this.pwd = pwd;
	}
	
	
}

由代码看出,实体类User中多了一个version属性,这个属性就是用来记录版本号的




User.hbm.xml代码:


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!-- This mapping demonstrates content-based discrimination for the table-per-hierarchy 
	mapping strategy, using a formula discriminator. -->

<!-- package:声明pojo类所在的包,如果不写,那么在class中的name里需要指明pojo类所在的包;
     schema:指数据库的模式,一个模式下可以有多张表。
 -->
<hibernate-mapping package="com.robert.pojo">

	<!-- class:指映射一个pojo类,
	         1)提供了公共的无参构造方法,通过反射产生对象。
	         2)属性用private修饰,并且生成对应的set/get方法。
	         3)类不能用final来修饰,hibernate会产生代理类(通过cglib)。
	     name:表示pojo类名;
	     table:表示pojo类对应的数据库中的表名;如果不写,默认是类名。
	 -->
	<class name="User" table="user">
		<id name="id" column="id">
			<generator class="native" />
		</id>
		<version name="version" />
		<property name="name" >
			<column name="name"></column>
		</property>
		<property name="pwd" />

	</class>

</hibernate-mapping>

实体类对应的配置文件中增加了一个version属性




HIbernateUtil和hibernate.cfg.xml代码和上面悲观锁中的代码相同,这里就不贴了


HibernateTest测试类中的代码:


package com.robert.test;

import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.metamodel.domain.Hierarchical;
import org.junit.Test;

import com.robert.pojo.User;
import com.robert.util.HibernateUtil;

public class HibernateTest {

	/**
	 * session的save方法,保存数据 session中状态改变:瞬时-->持久-->游离
	 */
	@Test
	public void testSave() {

		Session session = null;
		Transaction tx = null;
		User user = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();

			// 构造对象--瞬时状态
			user = new User();
			user.setName("小明2");
			user.setPwd("22221");
			session.save(user);

			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
			throw new HibernateException(e.getCause());
		} finally {
			HibernateUtil.closeSession();
		}
		// user处于游离状态,但是在内存中仍然存在
		System.out.println("name=" + user.getName());
	}

	@Test
	public void testUpdate() {
		
		Session session = null;
		Transaction tx = null;
		User user = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();
			
			user = (User) session.get(User.class, 1) ;
			user.setName("罗伯特") ;
			session.update(user) ;
			
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			tx.rollback();
			throw new HibernateException(e.getCause());
		} finally {
			HibernateUtil.closeSession();
		}
		// user处于游离状态,但是在内存中仍然存在
		System.out.println("name=" + user.getName());
	}

}


先运行testSave()方法,保存数据,


控制台打印的sql语句如下:


Hibernate: 
    insert 
    into
        user
        (version, name, pwd) 
    values
        (?, ?, ?)
name=小明2


数据库表中的数据如图所示:




执行testUpdate()方法,控制台打印的sql语句如下:


Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.version as version2_0_0_,
        user0_.name as name3_0_0_,
        user0_.pwd as pwd4_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        user 
    set
        version=?,
        name=?,
        pwd=? 
    where
        id=? 
        and version=?
name=罗伯特


由sql语句,可以看出更新条件有两个,一个是id,另一个是version


数据库表数据,如图:




由图中可以看到version由0变为了1,


接下来再测试一个同事开启两个session,看哪个数据可以更新


测试代码:


	/**
	 * 测试乐观锁
	 */
	@Test
	public void testOptimistic() {
		
		Session session = null;
		Transaction tx = null;
		User user = null;
		try {
			session = HibernateUtil.getSession();
			tx = session.beginTransaction();
			
			user = (User) session.get(User.class, 1) ;
			user.setName("罗伯特") ;
			
			Session session1 = HibernateUtil.getSession() ;
			Transaction tx1 = session1.beginTransaction() ;
			
			User user1 = (User) session.get(User.class, 1) ;
			user1.setName("珠海") ;
			session1.update(user1) ;
			tx1.commit() ;
			
			session.update(user) ;
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession();
		}
	}


控制台打印信息:


Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.version as version2_0_0_,
        user0_.name as name3_0_0_,
        user0_.pwd as pwd4_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        user 
    set
        version=?,
        name=?,
        pwd=? 
    where
        id=? 
        and version=?
Hibernate: 
    update
        user 
    set
        version=?,
        name=?,
        pwd=? 
    where
        id=? 
        and version=?
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.robert.pojo.User#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:465)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
	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:1258)
	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.robert.test.HibernateTest.testOptimistic(HibernateTest.java:95)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


数据库表数据如图:




由数据库中数据得知是user1数据更新到了数据库中,user的数据没有更新进去,报错了,

这是因为user和user1都从数据库中查询出了version是1的数据,但是user1先更新了数据,然后提交到了数据中,

此时,数据库中该数据的version变为了2,等到user也更新数据时,where条件中的version没有找到version是1的对应的数据,所以就报错了。


总结:


悲观锁 :优点是安全,缺点是并发效率低;


乐观锁 :安全性比悲观锁低,并发效率高;


如果数据需要大量修改,适用悲观锁。


如果数据时用来读取的,适用乐观锁。


































  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值