【Hibernate步步为营】--锁机制详解

上篇文章详细讨论了hql的各种查询方法,在讨论过程中写了代码示例,hql的查询方法类似于sql,查询的方法比较简单,有sql基础的开发人员在使用hql时就会变得相当的简单。Hibernate在操作数据库的同时也提供了对数据库操作的限制方法,这种方法被称为锁机制,Hibernate提供的锁分为两种一种是乐观锁,另外一种是悲观锁。通过使用锁能够控制数据库的并发性操作,限制用户对数据库的并发性的操作。

一、锁简介

锁能控制数据库的并发操作,通过使用锁来控制数据库的并发操作,Hibernate提供了两种锁来控制数据库的操作,分别是乐观锁和悲观锁。

乐观锁:大多数是采用数据版本的方式实现,一般在数据库中加入一个version字段,在读取数据的时候将version取出来,在保存数据的时候判断version的值是否小于数据库中的version值,如果小于不会更新,否则可更新数据库。

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

二、乐观锁

乐观锁是通过在数据库中添加一个名为version的字段来实现每次对数据的校验,在每次操作数据库的时候会自动更新version的值,这样每次操作的version值是不一样的,所以如果有并发操作时将会首先校验version值是否小于数据库的version值,如果小于的话不会更新数据库,它的具体使用方法如下示例:

2.1 映射实体Inventory.java

该类是映射的实体类,其中的属性quantity是数字的个数,在下面演示的示例中通过操作quantity来更新数据库的信息,另外的version字段是数据库信息的版本号,能通过校验来实现控制数据库的操作,它的具体控制方法要在映射文件中编写实现。

package com.src.hibernate;

public class Inventory {

	//主键id,标识号
	private String itemNo;
	public String getItemNo() {
		return itemNo;
	}
	public void setItemNo(String itemNo) {
		this.itemNo = itemNo;
	}

	//列表名称
	private String itemName;
	public String getItemName() {
		return itemName;
	}
	public void setItemName(String itemName) {
		this.itemName = itemName;
	}

	//个数
	private int quantity;
	public int getQuantity() {
		return quantity;
	}
	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}

        //版本号
        private int version;
        public int getVersion() {
               return version;
        }
        public void setVersion(int version) {
               this.version = version;
        }
 }

2.2  映射文件

在映射文件中需要添加<version>字段来实现对数据库版本号的映射,该标签的name属性要设置为对应实体中的version字段,这样在操作数据库时它会自动校验数据库版本号来限制对数据的操作。

<?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.src.hibernate.Inventory" table="t_inventory">
		<id name="itemNo">
			<generator class="assigned"/>
		</id>
		<version name="version"/>
		<property name="itemName" type="string"/>
		<property name="quantity" type="integer"/>
	</class>
</hibernate-mapping>

2.3  数据操作

测试上面的示例,在程序中添加了两个方法来加载并修改数据,其中的testLoad1()方法首先加载数据库中的数据,并修改字段Quantity的值,修改后保存数据,这时要设置断点,在提交事务时设置断点,停止对数据库的提交操作,此时执行testLoad2()方法,将会发出错误信息,不能更新数据库操作。

public void testLoad1(){
	Session session=null;
	try{
		//创建session对象
		session=HibernateUtils.getSession();
		//开启事务
		session.beginTransaction();
		Inventory inven=(Inventory)session.load(Inventory.class, "1001");
		System.out.println("p1-->name:"+inven.getItemName());
		inven.setQuantity(inven.getQuantity()-200);
		session.save(inven);
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}
public void testLoad2(){
	Session session=null;
	try{
		//创建session对象
		session=HibernateUtils.getSession();
		//开启事务
		session.beginTransaction();
		Inventory inven=(Inventory)session.load(Inventory.class, "1001");

		System.out.println("p2-->name:"+inven.getItemName());
		inven.setQuantity(inven.getQuantity()-200);
		session.save(inven);
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}

在不停止testLoad1()方法时执行testLoad2()方法,Hibernate发出如下语句,该语句说明testLoad2已经修改了数据库中的数据,但是testLoad1也在修改只是没有提交。

Hibernate: select inventory0_.itemNo as itemNo0_0_, inventory0_.version as version0_0_, inventory0_.itemName as itemName0_0_, inventory0_.quantity as quantity0_0_ from t_inventory inventory0_ where inventory0_.itemNo=?
p2-->name:zhangsan
Hibernate: update t_inventory set version=?, itemName=?, quantity=? where itemNo=? and version=?

testLoad2()提交更改后,接下来运行testLoad1()的断点,将会发出:Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) 的错误,说明两个同时在修改同一条数据,导致了并发操作,发出了错误提示。

三、悲观锁

悲观锁和乐观锁不同,它是通过数据库机制限制对数据库的操作的,通过使用方法的LockMode参数来配置对数据库的并发操作,在一次访问过程中将会把数据锁住(查询时),只要事务不提交那么任何用户都不能查看和修改,其它用户操作时将会被阻塞,不能同时操作,需要等待第一次访问完成后再进行其它的操作。具体方法如下示例。

3.1 实体Inventory.java

实体内容和乐观锁相同,不同的是在实体中不需要添加版本号属性,因为它不是通过判断版本号来限制操作的。

package com.src.hibernate;

public class Inventory {

	//主键id,标识号
	private String itemNo;
	public String getItemNo() {
		return itemNo;
	}
	public void setItemNo(String itemNo) {
		this.itemNo = itemNo;
	}

	//列表名称
	private String itemName;
	public String getItemName() {
		return itemName;
	}
	public void setItemName(String itemName) {
		this.itemName = itemName;
	}

	//个数
	private int quantity;
	public int getQuantity() {
		return quantity;
	}
	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}
}

3.2 映射文件

实体对应的映射文件也相当的简单,只需要配置相应的映射即可,没有其它复杂的操作,如下代码:

<?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.src.hibernate.Inventory" table="t_inventory">
		<id name="itemNo">
			<generator class="assigned"/>
		</id>

		<property name="itemName" type="string"/>
		<property name="quantity" type="integer"/>
	</class>
</hibernate-mapping>

3.3 测试代码

这里采用两个方法来测试操作,其中的testLoad1()方法首先查询数据然后修改数据,testLoad2()也是查询和修改数据,如下代码:

public void testLoad1(){
	Session session=null;
	try{
		//创建session对象
		session=HibernateUtils.getSession();
		//开启事务
		session.beginTransaction();
		Inventory inven=(Inventory)session.load(Inventory.class, "1001",LockMode.UPGRADE);
		System.out.println("p1-->name:"+inven.getItemName());

		inven.setQuantity(inven.getQuantity()-200);
		session.save(inven);
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}
public void testLoad2(){
	Session session=null;
	try{
		//创建session对象
		session=HibernateUtils.getSession();
		//开启事务
		session.beginTransaction();
		Inventory inven=(Inventory)session.load(Inventory.class, "1001",LockMode.UPGRADE);

		System.out.println("p2-->name:"+inven.getItemName());
		inven.setQuantity(inven.getQuantity()-200);
		session.save(inven);
		session.getTransaction().commit();
	}catch(Exception e){
		e.printStackTrace();
		session.getTransaction().rollback();
	}finally{
		HibernateUtils.closeSession(session);
	}
}

演示并发操作时在testLoad1()方法的打印处设置相应的断点,然后启动Debug调试,运行到断点后停止,此时运行testLoad2()方法,实现了对数据库的并发操作,在运行testLoad2()方法时Hibernate发出了SQL语句,但是却一直不能查询和检索数据,因为数据库中加了悲观锁,数据库限制了并发性的操作,所以Hibernate只是发出了命令但是一直没有操作权限,也就是说testLoad2()方法在执行时遭到了阻塞,操作被压到堆栈中等待上次的完成。当testLoad1()方法执行完后,将会从堆栈中继续获取testLoad2()的操作命令,这种乐观锁不仅限制了对数据的操作而且还能保证数据的完整性。

结语

Hibernate的锁机制限制了数据库的并发性的操作,功能很强大,在使用时建议使用悲观锁,因为悲观锁使用的是数据库的机制,限制彻底,而且能确保数据的完整性。在使用这两种锁时各有优缺点,大数据量建议采用乐观锁,它会直接提示错误,性能上会高于悲观锁,悲观锁会加大数据库的负担,大数据量的话容易出现问题。

【Hibernate步步为营】--锁机制详解,布布扣,bubuko.com

时间: 2024-08-01 22:47:25

【Hibernate步步为营】--锁机制详解的相关文章

Hibernate 所有缓存机制详解

Hibernate 所有缓存机制详解 hibernate提供的一级缓存 hibernate是一个线程对应一个session,一个线程可以看成一个用户.也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了. hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存.如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了. 缓存和连接池的区别:缓存和池都是放在内存里,实现是一样的

sqlserver锁机制详解(sqlserver查看锁)

简介 在SQL Server中,每一个查询都会找到最短路径实现自己的目标.如果数据库只接受一个连接一次只执行一个查询.那么查询当然是要多快好省的完成工作.但对于 大多数数据库来说是需要同时处理多个查询的.这些查询并不会像绅士那样排队等待执行,而是会找最短的路径执行.因此,就像十字路口需要一个红绿灯那 样,SQL Server也需要一个红绿灯来告诉查询:什么时候走,什么时候不可以走.这个红绿灯就是锁. 图1.查询可不会像绅士们那样按照次序进行排队 为什么需要锁 在开始谈锁之前,首先要简单了解一下事

mysql锁机制详解

前言 大概几个月之前项目中用到事务,需要保证数据的强一致性,期间也用到了mysql的锁,但当时对mysql的锁机制只是管中窥豹,所以本文打算总结一下mysql的锁机制. 本文主要论述关于mysql锁机制,mysql版本为5.7,引擎为innodb,由于实际中关于innodb锁相关的知识及加锁方式很多,所以没有那么多精力罗列所有场景下的加锁过程并加以分析,仅根据现在了解的知识,结合官方文档,说说自己的理解,如果发现有不对的地方,欢迎指正. 概述 总的来说,InnoDB共有七种类型的锁: 共享/排它

数据库事务、事务隔离级别以及锁机制详解

以下主要以MySQL(InnoDB引擎)数据库为讨论背景,纯属个人学习总结,不对的地方还请指出! 什么是事务? 事务是作为一个逻辑单元执行的一系列操作,要么一起成功,要么一起失败.一个逻辑工作单元必须有四个属性,称为 ACID(原子性.致性.隔离性和持久性)属性,只有这样才能成为一个事务. 数据库事物的四大特性(ACID): 1)原子性:(Atomicity) 务必须是原子工作单元:对于其数据修改,要么全都执行,要么全都不执行. 2)一致性:(Consistency) 事务在完成时,必须使所有的

mysql锁机制详解及死锁处理方式

为了给高并发情况下的mysql进行更好的优化,有必要了解一下mysql查询更新时的锁表机制.一.概述MySQL有三种锁的级别:页级.表级.行级.MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking):BDB存储引擎采用的是页面锁(page-levellocking),但也支持表级锁:InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁.MySQL这3种锁的特性可大致归纳如下:表级锁:开销小,加锁快:不会

[JavaEE]Hibernate 所有缓存机制详解

Hibernate提供的一级缓存 hibernate是一个线程对应一个session,一个线程可以看成一个用户.也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了. hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存.如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了. 缓存和连接池的区别:缓存和池都是放在内存里,实现是一样的,都是为了提高性能的.但有细微的差别,

Hibernate 所有缓存机制详解(转)

hibernate提供的一级缓存 hibernate是一个线程对应一个session,一个线程可以看成一个用户.也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了. hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存.如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了. 缓存和连接池的区别:缓存和池都是放在内存里,实现是一样的,都是为了提高性能的.但有细微的差别,

Hibernate延迟加载机制详解

摘自 http://blog.chinaunix.net/uid-20577907-id-3129234.html 1 延迟加载: 延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作. 在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在Hibernate3中还提供了对属性的延迟加载.下面我们就分别介绍这些种类的延迟加载的细节. A.实体对象的延迟加载: 如果想对实体对象使用延迟加载,必须要在实体的映射配置文

Android触摸屏事件派发机制详解与源码分析三(Activity篇)

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 该篇承接上一篇<Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)>,阅读本篇之前建议先阅读. 1 背景 还记得前面两篇从Android的基础最小元素控件(View)到ViewGroup控件的触摸屏事件分发机制分析吗?你可能看完会有疑惑,View的事件是ViewGro