现在的位置: 首页 > 综合 > 正文

Hibernate悲观锁和乐观锁

2018年06月05日 ⁄ 综合 ⁄ 共 4631字 ⁄ 字号 评论关闭

悲观锁

悲观锁通常是由数据库机制实现的,在整个过程中把数据锁住(查询时),只要事物不释放(提交/回滚),那么任何用户都不能查看或修改。

下面我们通过一个案例来说明。

案例:假设货物库存为1000,当核算员1取出了数据准备修改,但临时有事,就走了。期间核算员2取出了数据把数量减去200,然后核算员1回来了把刚才取出的数量减去200,这就出现了一个问题,核算员1并没有在800的基础上做修改。这就是所谓的更新丢失,采用悲观锁可以解决。

Inventory.java:

public class Inventory {

	/* 存货编号 */
	private String itemNo;
	/* 存货名称 */
	private String itemName;
	/* 存货数量 */
	private int quantity;

	//省略setter和getter方法
}

Inventory.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="com.lixue.bean.Inventory" table="t_inventory">
		<!-- 主键手动分配 -->
		<id name="itemNo">
			<generator class="assigned"/>
		</id>
		<!-- 映射属性 -->
		<property name="itemName"/>
		<property name="quantity"/>
	</class>
</hibernate-mapping>

测试类:

核算员1通过悲观锁的方式加载数据,并对数据进行修改!

public void testLoad1() {
		Session session = null;
		try {
			session = HibernateUtils.getSession();
			session.beginTransaction();
			/*在加载的时候就加上一把悲观锁,让其他用户都无法访问*/
			Inventory inv = (Inventory) session.load(Inventory.class, "1001", LockMode.UPGRADE);
			/*获取数据*/
			System.out.println("opt1-->itemNo=" + inv.getItemNo());
			System.out.println("opt1-->itemName=" + inv.getItemName());
			System.out.println("opt1-->quantity=" + inv.getQuantity());
			/*数量减去200*/
			inv.setQuantity(inv.getQuantity() - 200);

			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
			session.getTransaction().rollback();
		} finally {
			HibernateUtils.closeSession(session);
		}
	}

核算员2和核算员1的操作相同,都是对数据库中的数据进行修改!

public void testLoad2() {
		Session session = null;
		try {
			session = HibernateUtils.getSession();
			session.beginTransaction();
			/*在加载数据的时候就加上一把锁,让其他人无法获取数据*/
			Inventory inv = (Inventory) session.load(Inventory.class, "1001", LockMode.UPGRADE);
			/*获取真实数据*/
			System.out.println("opt2-->itemNo=" + inv.getItemNo());
			System.out.println("opt2-->itemName=" + inv.getItemName());
			System.out.println("opt2-->quantity=" + inv.getQuantity());
			/*库存减去200*/
			inv.setQuantity(inv.getQuantity() - 200);

			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
			session.getTransaction().rollback();
		} finally {
			HibernateUtils.closeSession(session);
		}
	}

注:两个核算员做的操作相同,如果加了悲观锁之后,核算员取出了数据并对数据进行修改,在核算员1没有提交事物之前,核算员2是不能对数据进行访问的,只能处于等待状态。知道核算员1把事物提交了之后,核算员2才有机会对数据库中的数据进行操作。

通过上面悲观锁的案例我们可以发现,悲观锁最大的好处就是可以防止更新丢失,当核算员1在处理数据的时候,核算员2只能处于等待状态,只有核算员1提交了事物之后,核算员2才有机会修改数据。但是也存在一个很大的问题,那就是,如果核算员1将数据查询出来后人就走掉了,那么其他人就得等上大半天,非常浪费时间,为了解决这个问题,我们可以使用乐观锁。

乐观锁

乐观锁并不是真正意义上的锁,大多数情况下是采用数据版本(version)的方式实现,一般在数据库中加入一个version字段,在读取数据的时候就将version读取出来,在保存数据的时候判断version的值是否小于数据库的version值,如果小于则不予更新,否则给予更新。

乐观锁下的javaBean设置,Inventory.java:

public class Inventory {
	
	/*存货编号*/
	private String itemNo;
	/*存货名称*/
	private String itemName;
	/*存货数量*/
	private int quantity;
	/*数据版本*/
	private int version;

	//省略setter和getter方法
}

Inventory.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<!-- 在class标签中加上optimistc-lock属性,其值为版本信心 -->
	<class name="com.lixue.bean.Inventory" table="t_inventory" optimistic-lock="version">
		<!-- 主键映射 -->
		<id name="itemNo">
			<generator class="assigned"/>
		</id>
		<!-- 数据版本,必须在主键后面的位置 -->
		<version name="version"/>
		<!-- 基本属性映射 -->
		<property name="itemName"/>
		<property name="quantity"/>
	</class>
</hibernate-mapping>

注:使用乐观锁的映射文件有规定即version字段的映射必须在主键ID之后第一个被映射。

测试:

核算员1在乐观锁的情况下处理数据:

public void testLoad1() {
		Session session = null;
		try {
			session = HibernateUtils.getSession();
			session.beginTransaction();
			/*乐观锁下加载数据*/
			Inventory inv = (Inventory)session.load(Inventory.class, "1001");
			/*实际获取数据*/
			System.out.println("opt1-->itemNo=" + inv.getItemNo());
			System.out.println("opt1-->itemName=" + inv.getItemName());
			System.out.println("opt1-->version=" + inv.getVersion());
			System.out.println("opt1-->quantity=" + inv.getQuantity());
			/*数量减去200*/
			inv.setQuantity(inv.getQuantity() - 200);
			
			session.getTransaction().commit();
		}catch(Exception e) {
			e.printStackTrace();
			session.getTransaction().rollback();
		}finally {
			HibernateUtils.closeSession(session);
		}
	}

核算员2在乐观锁的情况下处理数据(核算员2可以在核算员1未提交数据的前提下处理数据)

public void testLoad2() {
		Session session = null;
		try {
			session = HibernateUtils.getSession();
			session.beginTransaction();
			/*乐观锁下加载数据*/
			Inventory inv = (Inventory)session.load(Inventory.class, "1001");
			/*实际获取数据*/
			System.out.println("opt2-->itemNo=" + inv.getItemNo());
			System.out.println("opt2-->itemName=" + inv.getItemName());
			System.out.println("opt2-->version=" + inv.getVersion());
			System.out.println("opt2-->quantity=" + inv.getQuantity());
			/*数量减去200*/
			inv.setQuantity(inv.getQuantity() - 200);
			
			session.getTransaction().commit();
		}catch(Exception e) {
			e.printStackTrace();
			session.getTransaction().rollback();
		}finally {
			HibernateUtils.closeSession(session);
		}
	}			

注:在核算员取出数据将数量减去200之后并未提交的前提下,核算员2也可以操作数据,这就有别于悲观锁,当核算员2操作了数据并且提交之后,数据库中数据版本version就会加1,那么当核算员1在回来进行事物提交时就会出现错误提示即数据已更新,请重新加载。

总结:悲观锁会影响高并发,所以用乐观锁比较好。

【上篇】
【下篇】

抱歉!评论已关闭.