欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

悲观锁

程序员文章站 2022-05-04 15:14:16
...

为了避免读写数据不及时,导致脏数据产生的问题,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的对应的数据,所以就报错了。


总结:


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


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


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


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


































相关标签: 乐观锁 悲观锁