Hibernate在应用层对并发事务的控制

Hibernate与事务处理一文中,介绍了事务以及并发事务处理时的问题,和针对于这些问题在数据库层所能做的隔离级别,但是在选择read commit和repeatable read两个隔离级别时,如果考虑到performance和scalability,可以选择一个折衷的方案,也就是在数据库中的隔离级别选择read commit,而通过对应用程序的控制,可以达到repeatable read的效果。Hibernate在程序中控制并发的事务处理上,也有自己的方法,本文在Hibernate与事务处理基础上,对Hibernate的这些并发事务处理方法进行介绍。

数据库隔离级别可以通过设置hibernate的属性文件,来改变数据库默认的隔离级别:

<class name="Student" table="TSTUDENT">   

...   

<!---->class>  

在Student.java的pojo文件中添加:

public class Student {   

....   

private int version;   

...   

}  

version属性是不能被应用所改变的,所以仅仅提供get方法即可。但是versionnumber是怎样在应用层提供对事务的并发处理机制呢?原理是这样的:在hibernate的事务A(在这里假设事务的范围和Session范围一致,每个Session对应一个persistentcontext)中,执行每一个DML操作,都会先检查一下对象的version属性,假如从数据库中得到的student实例的version属性是1,此时student已经加载到Session对应的persistentcontext中,如果对student对象进行改变,在Session.flush()的时候,则将persistentcontext中的student对象的version属性设置为2,然后在保存student对象时,将对象对应的记录version字段更新为2。

注意的是,如果并发的另一个事务B在事务A进行保存操作之前,已经将version更新为2或者更高值的student存入数据库,那A在保存student之前,需要检查该persistentobject对应的记录的version为1(因为在从数据库取得persistentobject的时候,version为1)的行的个数,如果行数为0,则抛出StaleObjectStateException,表明存在其他的事务更新了数据库,那这样的话,A事务的更新操作就不会覆盖B事务的更新操作,达到了避免(3)问题中的secondlostupdate(见Hibernate与事务处理)。

当然避免了secondlostupdate是不够的,因为是不是这样就能够具有repeatableread的隔离级别呢?比较一下readcommit和repeatableread的隔离级别的差别,readcommit是读事务允许其他事务并发执行,而repeatableread是读事务不允许写事务并发执行,在hibernate中,通过Session对应的persistentcontext来实现后者,具体是在一个事务A中(假设事务的范围和Session范围一致),读取student对象到persistentcontext,此时事务B更改了student对象对应的记录并进行持久化,根据数据库的默认隔离级别readcommit,那么A事务如果再读取student对象,那就是产生unrepeatableread,其实不然,正是因为hibernate的persistentcontext,使得A事务再次读取student不是从数据库中读取,而是从当前的Session读取,因此避免了unrepeatableread,同时使得具有类似于数据库隔离级别repeatableread的隔离效果。

当然我们这里假设的是事务的范围和Session范围一致,但是当用户操作中途停留时间过长,则需要将不同的事务在同一conversation下实现,要保证不同事务共用Session可以通过Hibernate的FlushMode来实现。当然也可以保证Session和事务的对应,通过在不同Session中传递detachedobject来实现。

对于Hibernate在应用层对并发事务的控制方法还有timestamp,pessimisticlocking,这里先不做介绍。

相关推荐