首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 企业软件 > 行业软件 >

Hibernate兑现贯穿三层的乐观锁

2012-07-26 
Hibernate实现贯穿三层的乐观锁无论Hibernate还是Toplink,都支持乐观锁机制。在Toplink中实现贯穿3层的乐观

Hibernate实现贯穿三层的乐观锁

无论Hibernate还是Toplink,都支持乐观锁机制。在Toplink中实现贯穿3层的乐观锁很容易,但Hibernate缺省不支持三层环境下的乐观锁,为了实现这个功能,我费了一番功夫。

?

所谓乐观锁,是指在实体上增加一个字段 version (Hibernate目前只支持int,Toplink可以是long),提交实体时,采用这样的update语句

?

update T set a=xx, version=1 where id=1 and version=0
?

根据返回结果中修改记录的条数来判断是否修改了版本正确的对象,如果条数为0标示此id的对象版本已被修改,抛出异常。

?

Hibernate的实现只考虑到了两层(Server端)的乐观锁,只能作到Server端读出到写入对象这段间隔内的并发控制。实际上,我们需要并发控制的间隔往往更长。在三层系统内,我们尝尝要将Client读取对象到Server写入对象作为一个整体进行并发控制。考虑以下步骤:

?

?

    ??Client ?请求对象 id=1??Server 读取数据库,返回[id=100,value='abc',version=1]??Client ?进行展示,用户在界面上将 'abc' 改为 'xyz'??Client ?传回数据,要求将[id=100, version=1]的对象值修改为'xyz'??Server 开启事务,读取数据库中 id=100的对象,将value设为'xyz'??Server 提交事务

以上步骤1~6应该作为一个整体,由于用户是根据[version=1]的对象状态作出将 value改为'xyz'的操作,所以从用户看到数据到最后提交的过程中,这个对象都不能有修改,不然可能在用户不知情的状况下覆盖掉别人的修改。

?

在Toplink中要实现以上的并发控制,只需要在步骤5中读出id=100的对象后, setVersion(1),最后的SQL中version取值就是1

但在Hibernate中,setVersion是不起作用的。Hibernate最后生成的SQL语句中where version= xx 中的取值是对象从session中被读出来的初始值,而不是对象对象被提交时的值。所以如果读出对象时version=2,即使手工serVersion(1),最后的SQL中version取值还是2,也就不会抛异常。

以上,Hibernate的实现只能防止步骤5、6之间的并发修改,不能防止1~4之间数据被修改。我们知道,前四个步骤的间隔远大于后两个步骤,如果不能解决这个问题,Hibernate的乐观锁的实用价值就不大了。

奇怪的是,这应该是个很普遍的问题,但在网上搜到的问题和方案却不多,stackoverflow上给了个方法也不管用(后面会提到),只能自己想办法。

首先想到的是Hijack源码,把生成SQL时的语句改成取最新的Version,这应该是最根本的方法。但公司对jar包管理较严,所以不可行。

其次是stackoverflow上的方法,写一个HibernateIntecetor,在onFlushDirty中判断新旧version不一样则手工抛异常,这个方法也不可行,因为Hibernate自己更新对象时也会去修改objectVersion并触发onFlushDirty,同样的问题也存在于setVersion(int version)方法中,如果在setVerison里进行判断,由于我们的mapping annotation配置在property上,初始化对象时会调用这个setter把version从0改成数据库里的值,一样会抛出不该抛的异常。

?

总结一下,既然不能修改最后拿version的方法(修改源码),只能在修改version时进行判断(interceptor,setter);但Hibernate进行的修改不应该抛异常,只对所有手工进行的不一致的修改抛异常,该怎么办呢?

我的解决方法是对version修改给出两个函数,一个是暴露出去的resetVersion,供手工修改,在这个方法类进行判断并抛异常;另一个是内部的setter,供Hibernate内部使用,这个setter不会进行判断。为了防止内部setter被误调用,将其设为default access

?

接口

?

public interface OptimisticLockSupported {    String PROPERTY_OBJECT_VERSION = "objectVersion";    int getObjectVersion();    /**     * Public Setter of objectVersion, child classes should throw     * StaleObjectStateException if objectVersion is changed.     */    void resetObjectVersion(int objectVersion);}
?

?

抽象类

?

@MappedSuperclasspublic abstract class AbstractOptimisticLockSupportedEntity implements OptimisticLockSupported, Serializable {    /** *  */    private static final long serialVersionUID = 128597519171260732L;    private int objectVersion;    @Version    public int getObjectVersion() {        return objectVersion;    }    /**     * Setter of obejctVersion,  for Hibernate only.     *      * @param objectVersion     */    void setObjectVersion(int objectVersion) {        this.objectVersion = objectVersion;    }    /**     * child classes should override this function and throw     * StaleObjectStateException if objectVersion is changed. Do not implement     * here because constructor of UnsupportedOperationException need id which     * does not exist in this class.     */    public void resetObjectVersion(int objectVersion) {        throw new UnsupportedOperationException();    }}
?

??再下一层的AbstractIdEntity中重载resetObjectVersion

?

? /**

     * Implement {@code resetObjectVersion(int objectVersion)} here because     * constructor of {@code StaleObjectStateException} need id which does not     * exist in {@code OptimisticLockSupported}.     * <p>     * In the implementation, any change of objectVersion will throw     * StaleObjectStateException, below is an example:     * <p>     * id (say 100) and objectVersion (say 1) are passed from client to server,     * as a process in server side, you may     * <ol>     * <li>Find entity (say user) by id (100)     * <li>Reset object version of user to 1 by invoke     * {@code user.resetObjectVersion(1L);}     * </ol>     * If the current object version of user is 1, everything will be OK. But if     * it is 2, a StaleObjectStateException will be thrown to indicate that user     * has been modified since it was sent to client.     *      */    @Override    public void resetObjectVersion(int objectVersion) {        if (getObjectVersion() != objectVersion) {            throw new StaleObjectStateException(getClass().getName(), getId());        }        setObjectVersion(objectVersion);    }

?

??这样作,算是解决了此问题,但相比Toplink,整体上很不优雅,不是一个total solution。

?

?我用的Hibernate是3.3.2.GA,不知道在后面的版本中是否会修复此问题。

?

?

?

热点排行