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写入对象作为一个整体进行并发控制。考虑以下步骤:

 

 

  1.   Client  请求对象 id=1
  2.   Server 读取数据库,返回[id=100,value='abc',version=1]
  3.   Client  进行展示,用户在界面上将 'abc' 改为 'xyz'
  4.   Client  传回数据,要求将[id=100, version=1]的对象值修改为'xyz'
  5.   Server 开启事务,读取数据库中 id=100的对象,将value设为'xyz'
  6.   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);

}
 

 

抽象类

 

@MappedSuperclass
public 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,不知道在后面的版本中是否会修复此问题。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值