Hibernate 之 Locking
在我们业务实现的过程中,往往会有这样的需求:保证数据访问的排他性,也就是我正在访问的数据,别人不能够访问,或者不能对我的数据进行操作.面对这样的需求,就需要通过一种机制来保证这些数据在一定的操作过程中不会被他人修改,这种机制就是我们今天要说的Locking 即"锁".由此我们可以得出一个小结论,锁主要是解决并发性问题.
Hibernate支持两种锁机制:"悲观锁"(Pessimistic Locking )和"乐观锁"(Optimistic Locking )
先来看悲观锁
通过悲观这两个字我们能够看出,它对数据被外界操作所采取的态度是悲观的,保守的.也就是在整个数据处理的过程中,,它都将数据处于锁定状态.悲观锁,通常是由数据库机制实现的,在整个过程中把数据锁住(查询时),只要事务不释放(提交/回滚)那么任何用户都不能查看或修改.需要等待资源释放.
接下来将通过一个简单查询语句来解释Hibernate是如何实现悲观锁.
- //查询用户为"jnqqls"的用户
- String hqlStr ="from User as user where user.name='Jnqqls'";
- Query query = session.createQuery(hqlStr);
- // 加锁
- query.setLockMode("user",LockMode.UPGRADE);
- // 执行查询,获取数据
- List userList = query.list();
通过查看运行时Hibernate的执行语句会发现,它是通过数据库的for update子句来实现了悲观锁的机制.
根据悲观锁的保守特点,在并发比较高的情况下不建议使用.特别是事务内的执行周期特别的长的情况下,其他请求资源都处于等待状态,导致数据库性能的大量开销.
接下来来看乐观锁
乐观锁(其实根本上不是锁,跟数据库没有关系.而是解决冲突的检测手段,机制是在数据库中加字段,例如增加一个版本号或者是时间戳).
Hibernate实现乐观锁机制的原理
大多数的使用是采用数据版本的方式(version)实现,一般在数据库中加入一个version字段在读取数据的时候将version读取出来,在保存数据的时候判断version的值是否小于数据库中的version值,如果小于不予更新,否则给予更新.
了解原理之后接下来我们将会通过代码来了解Hibernate是如何实现乐观锁.
第一步,在Hibernate配置文件中添加version字段,Hibernate会自动进行相关的锁处理.
- <?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.tgb.hibernate.User" table="t_user" optimistic-lock="version">
- <id name="id">
- <generator class="assigned"/>
- </id>
- <version name="version"/>
- <property name="name"/>
- <property name="age"/>
- </class>
- </hibernate-mapping>
在此配置的过程中需要注意version的结点必须在id结点之后.此处version的作用注意是存放着用户的版本信息.
下面编写一段代码,将用户的年龄减少2岁.
- public void testLoad() {
- Session session = null;
- try {
- //一个获取session的工具.
- session = HibernateUtils.getSession();
- //开启事务
- session.beginTransaction();
- //加载用户id为1001
- User User = (User)session.load(User.class, "1001");
- //打印相关信息
- System.out.println("opt1-->Id=" + user.getId());
- System.out.println("opt1-->Name=" + user.getName());
- System.out.println("opt1-->Version=" + user.getVersion());
- System.out.println("opt1-->Age=" + user.getAge());
- user.setAge(user.getAge() - 2);
- //提交事务
- session.getTransaction().commit();
- }catch(Exception e) {
- e.printStackTrace();
- session.getTransaction().rollback();
- }finally {
- //关闭session
- HibernateUtils.closeSession(session);
- }
- }
通过操作可以发现,在每次更新User信息的时候,数据库中的version字段都在递增,如果我们在session提交之前开启另外一个session2对User年龄进行操作的话会出现错误提示.
org.hibernate.StaleObjectStateException:Row was updated ordeleted by another transaction(or unsaved-value mapping was incorrect):…………..
我们可以进行异常捕获版本错误并对用户进行相应的提示,例如"数据已经被修改".
Hibernate 锁小结
悲观锁容易出现资源等待现象,因为对资源的完整占有.如果并发逻辑的时间过长的话则会导致系统其他并发逻辑的等待,会大大降低系统的并发性,并加大数据库的开销.
乐观锁因为采用版本机制,不会出现资源等待的现象,它会在提交数据的那一瞬间去判断版本的大小,如果出现并发修改的情况则会抛出异常错误,对于乐观锁而言,异常捕获显得尤为重要.