首先,为什么要有锁这种概念和技术呢?
什么是锁( locking )
业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算处理中,我们希望针对某个 cut-off 时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓的 “锁” ,即给我们选定的目标数据上锁,使其无法被其他程序修改。Hibernate
支持两种锁机制:即通常所说的 “悲观锁( Pessimistic Locking )”和 “乐观锁( Optimistic Locking )” 。
明白了吧,其实就为了在对数据进行操作的时候,保持一对一性,在你改数据的时候,就不允许其它别人来动。我总结为:一个数据一次同时只被一个人修改,这就是原则。
什么是悲观锁哟?
悲观锁( Pessimistic Locking )
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
select * from account where name="user" for update
什么是乐观锁呢?
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
优点
缺点
添加属性
<hibernate-mapping> <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version" > …… </class> </hibernate-mapping>
添加描述符
<hibernate-mapping> <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version" > <id name="id" column="id" type="java.lang.Integer" > <generator class="native"> </generator> </id> <version column="version" name="version" type="java.lang.Integer" /> …… </class> </hibernate-mapping>
Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("name","Erica")); List userList = criteria.list(); TUser user =(TUser)userList.get(0); Transaction tx = session.beginTransaction(); user.setUserType(1); // 更新 UserType 字段 tx.commit();
Session session= getSession(); Criteria criteria = session.createCriteria(TUser.class); criteria.add(Expression.eq("name","Erica")); Session session2 = getSession(); Criteria criteria2 = session2.createCriteria(TUser.class); criteria2.add(Expression.eq("name","Erica")); List userList = criteria.list(); List userList2 = criteria2.list();TUser user =(TUser)userList.get(0); TUser user2 =(TUser)userList2.get(0); Transaction tx = session.beginTransaction(); Transaction tx2 = session2.beginTransaction(); user2.setUserType(99); tx2.commit(); user.setUserType(1); tx.commit();
补充内容
Hibernate乐观锁实现方式有两种:Version和Timestamp
下面用代码来验证:
一、Version:
create table studentVersion (id varchar(32),name varchar(32),ver int);
2、POJO
package Version; create table studentVersion (id varchar(32),name varchar(32),ver int); public class Student ...{ private String id; private String name; private int version; public String getId() ...{ return id; } public void setId(String id) ...{ this.id = id; } public String getName() ...{ return name; } public void setName(String name) ...{ this.name = name; } public int getVersion() ...{ return version; } public void setVersion(int version) ...{ this.version = version; } }
3、Student.hbm.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Mapping file autogenerated by MyEclipse - Hibernate Tools --> <hibernate-mapping> <class name="Version.Student" table="studentVersion" > <id name="id" unsaved-value="null"> <generator class="uuid.hex"></generator> </id> <!--version标签必须跟在id标签后面--> <version name="version" column="ver" type="int"></version> <property name="name" type="string" column="name"></property> </class> </hibernate-mapping>
4、Hibernate.cfg.xml
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <!-- Generated by MyEclipse Hibernate Tools. --> <hibernate-configuration> <session-factory> <property name="connection.username">root</property> <property name="connection.url"> jdbc:mysql://localhost:3306/schoolproject?characterEncoding=gb2312&useUnicode=true </property> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="myeclipse.connection.profile">mysql</property> <property name="connection.password">1234</property> <property name="connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="hibernate.dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="hibernate.show_sql">true</property> <property name="current_session_context_class">thread</property> <property name="jdbc.batch_size">15</property> <mapping resource="Version/Student.hbm.xml" /> </session-factory> </hibernate-configuration>
5、测试代码:
package Version; import java.io.File; import java.util.Iterator; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; public class Test ...{ public static void main(String[] args) ...{ String filePath=System.getProperty("user.dir")+File.separator+"src/Version"+File.separator+"hibernate.cfg.xml"; File file=new File(filePath); System.out.println(filePath); SessionFactory sessionFactory=new Configuration().configure(file).buildSessionFactory(); Session session=sessionFactory.openSession(); Transaction t=session.beginTransaction(); Student stu=new Student(); stu.setName("tom11"); session.save(stu); t.commit(); /**//* * 模拟多个session操作student数据表 */ Session session1=sessionFactory.openSession(); Session session2=sessionFactory.openSession(); Student stu1=(Student)session1.createQuery("from Student s where s.name='tom11'").uniqueResult(); Student stu2=(Student)session2.createQuery("from Student s where s.name='tom11'").uniqueResult(); //这时候,两个版本号是相同的 System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion()); Transaction tx1=session1.beginTransaction(); stu1.setName("session1"); tx1.commit(); //这时候,两个版本号是不同的,其中一个的版本号递增了 System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion()); Transaction tx2=session2.beginTransaction(); stu2.setName("session2"); tx2.commit(); } }
运行结果:
Hibernate: insert into studentVersion (ver, name, id) values (?, ?, ?)
Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.name as name0_ from studentVersion student0_ where student0_.name='tom11'
Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.name as name0_ from studentVersion student0_ where student0_.name='tom11'
v1=0--v2=0
Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?
v1=1--v2=0
Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Version.Student#4028818316cd6b460116cd6b50830001]
可以看到,第二个“用户”session2修改数据时候,记录的版本号已经被session1更新过了,所以抛出了红色的异常,我们可以在实际应用中处理这个异常,例如在处理中重新读取数据库中的数据,同时将目前的数据与数据库中的数据展示出来,让使用者有机会比较一下,或者设计程序自动读取新的数据
注意:如果手工设置stu.setVersion()自行更新版本以跳过检查,则这种乐观锁就会失效,应对方法可以将Student.java的setVersion设置成private