Hibernate3 学习(十)

12Locking

Hibernate 透过两种 Locking 机制来保证数据在操作过程中不会被干扰。

121悲观锁定(Pessimistic Locking

在多个客户端可能读取同一笔数据或同时更新一笔数据的情况下,必须要有访问控制的手段,防止同一个数据被修改而造成混乱,最简单的手段就是对数据进行锁定,在自己进行数据读取或更新等动作时,锁定其它客户端不能对同一笔数据进行任何的动作。

悲观锁定(Pessimistic Locking)一如其名称所示,悲观的认定每次资料存取时,其它的客户端也会存取同一笔数据,因此对该笔数据进行锁定,直到自己操作完成后解除锁定。

悲观锁定通常透过系统或数据库本身的功能来实现,依赖系统或数据库本身提供的锁定机制,Hibernate即是如此,可以利用QueryCriteriasetLockMode()方法来设定要锁定的表或列(Row)及其锁定模式,可设定的锁定模式有以下的几个:
LockMode.UPGRADE
:利用数据库的for update子句进行锁定。
LockMode.UPGRADE_NOWAIT
:使用for update nowait子句进行锁定,在Oracle数据库中使用。

一个设定锁定的例子如下:

Session session = sessionFactory.openSession();

Query query = session.createQuery("from User user");

query.setLockMode("user", LockMode.UPGRADE);

List users = query.list();

...

session.close();

这个程序片段会使用以下的SQL进行查询:

Hibernate: select user0_.id as id, user0_.name as name0_, user0_.age as age0_

from user user0_ for update

也可以在使用Sessionload()或是lock()时指定锁定模式以进行锁定。

另外还有三种加锁模式Hibernate内部自动对数据进行锁定,与数据库无关:
LockMode.WRITE
:在insertupdate时进行锁定,Hibernate会在save()方法时自动获得锁定。
LockMode.READ
:在读取记录时Hibernate会自动获得锁定。
LockMode.NONE
:没有锁定。

如果数据库不支持所指定的锁定模式,Hibernate会选择一个合适的锁定替换,而不是丢出一个例外。

122乐观锁定(Optimistic Locking

悲观锁定假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,因而对数据采取了数据库层次的锁定状态,在锁定的时间内其它的客户不能对数据 进行存取,对于单机或小系统而言,这并不成问题,然而如果是在网络上的系统,同时间会有许多联机,如果每一次读取数据都造成锁定,其后继的存取就必须等 待,这将造成效能上的问题,造成后继使用者的长时间等待。

乐观锁定(Optimistic locking)则乐观的认为资料的存取很少发生同时存取的问题,因而不作数据库层次上的锁定,为了维护正确的数据,乐观锁定使用应用程序上的逻辑实现版本控制的解决。

在不实行悲观锁定策略的情况下,数据不一致的情况一但发生,有几个解决的方法,一种是先更新为主,一种是后更新的为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁定。

Hibernate中透过版本号检查来实现后更新为主,这也是Hibernate所推荐的方式,在数据库中加入一个version字段记录,在读取数据时 连同版本号一同读取,并在更新数据时比对版本号与数据库中的版本号,如果等于数据库中的版本号则予以更新,并递增版本号,如果小于数据库中的版本号就丢出 例外。

实际来透过范例了解Hibernate的乐观锁定如何实现,首先在数据库中新增一个表格:

CREATE TABLE user (

    id INT(11) NOT NULL auto_increment PRIMARY KEY,

    version INT,

    name VARCHAR(100) NOT NULL default '',

    age INT

);

这个user表格中的version用来记录版本号,以供Hibernate实现乐观锁定,接着设计User类别,当中必须包括version属性:

User.java

package onlyfun.caterpillar;

 

public class User {

    private Integer id;

    private Integer version; // 增加版本属性  

    private String name;

    private Integer age;

   

    public User() {

    }

 

    public Integer getId() {

        return id;

    }

 

    public void setId(Integer id) {

        this.id = id;

    }

 

    public Integer getVersion() {

        return version;

    }

 

    public void setVersion(Integer version) {

        this.version = version;

    }

   

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

   

    public Integer getAge() {

        return age;

    }

 

    public void setAge(Integer age) {

        this.age = age;

    }

}

在映射文件的定义方面,则如下所示:

User.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">

 

<hibernate-mapping>

 

    <class name="onlyfun.caterpillar.User"

           table="user"

           optimistic-lock="version">

          

        <id name="id" column="id" type="java.lang.Integer">

            <generator class="native"/>

        </id>

 

        <version name="version"

                 column="version"

                 type="java.lang.Integer"/>

                

        <property name="name" column="name" type="java.lang.String"/>

 

        <property name="age" column="age" type="java.lang.Integer"/>

       

    </class>

 

</hibernate-mapping>

注意<version>卷标必须出现在<id>卷标之后,接着您可以试着在数据库中新增数据,例如:

User user = new User();

user.setName("caterpillar");

user.setAge(new Integer(30));

Session session = sessionFactory.openSession();

Transaction tx =  session.beginTransaction();

session.save(user);

tx.commit();

session.close();

您可以检视数据库中的数据,每一次对同一笔数据进行更新,version字段的内容都会自动更新,接着来作个实验,直接以范例说明:

// 有使用1者开启了一个session1

Session session1 = sessionFactory.openSession();

// 在这之后,马上有另一个使用者2开启了session2

Session session2 = sessionFactory.openSession();

       

Integer id = new Integer(1);

 

// 使用者1查询数据       

User userV1 = (User) session1.load(User.class, id);

// 使用者2查询同一笔数据

User userV2 = (User) session2.load(User.class, id);

 

// 此时两个版本号是相同的

System.out.println(" v1 v2 "

                + userV1.getVersion().intValue() + " "

                + userV2.getVersion().intValue());

       

Transaction tx1 = session1.beginTransaction();

Transaction tx2 = session2.beginTransaction();

 

// 使用者1更新数据       

userV1.setAge(new Integer(31));

tx1.commit();

 

// 此时由于数据更新,数据库中的版本号递增了

// 两笔数据版本号不一样了

System.out.println(" v1 v2 "

                + userV1.getVersion().intValue() + " "

                + userV2.getVersion().intValue());

       

// userV2 age 资料还是旧的

// 数据更新

userV2.setName("justin");

// 因版本号比数据库中的旧

// 送出更新数据会失败,丢出StableObjectStateException例外

tx2.commit();

       

session1.close();

session2.close();

运行以下的程序片段,会出现以下的结果:

Hibernate:

select user0_.id as id0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_

from user user0_ where user0_.id=?

 

Hibernate:

select user0_.id as id0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_

from user user0_ where user0_.id=?

 v1 v2 0 0

 

Hibernate:

update user set version=?, name=?, age=? where id=? and version=?

 v1 v2 1 0

 

Hibernate:

update user set version=?, name=?, age=? where id=? and version=?

16:11:43,187 ERROR AbstractFlushingEventListener:277 - Could not synchronize database state with session

org.hibernate.StaleObjectStateException:

  Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [onlyfun.caterpillar.User#1]

    at org.hibernate.persister.entity.BasicEntityPersister.check(BasicEntityPersister.java:1441)

由于新的版本号是1,而userV2的版本号还是0,因此更新失败丢出StableObjectStateException,您可以捕捉这个例外作善后处理,例如在处理中重新读取数据库中的数据,同时将目前的数据与数据库中的数据秀出来,让使用者有机会比对不一致的数据,以决定要变更的部份,或者您可以 设计程序自动读取新的数据,并比对真正要更新的数据,这一切可以在背景执行,而不用让您的使用者知道。

要注意的是,由于乐观锁定是使用系统中的程序来控制,而不是使用数据库中的锁定机制,因而如果有人特意自行更新版本讯息来越过检查,则锁定机制就会无效, 例如在上例中自行更改userV2version属性,使之与数据库中的版本号相同的话就不会有错误,像这样版本号被更改,或是由于数据是由外部系统而来,因而版本信息不受控制时,锁定机制将会有问题,设计时必须注意。

13ValidatableLifecycleInterceptor

分别透过这三个接口,来进行数据验证、于 CRUDCreate Retrieve Update Delete)作对应动作、栏截动作。

131 Lifecycle 接口、Validatable 接口

可以在实体对象定义时实作Lifecycle接口,这个接口定义如下:

Lifecycle.java

package org.hibernate.classic;

 

import java.io.Serializable;

import org.hibernate.CallbackException;

import org.hibernate.Session;

 

public interface Lifecycle {

    public static final boolean VETO = true;

    public static final boolean NO_VETO = false;

 

    public boolean onSave(Session s) throws CallbackException;

    public boolean onUpdate(Session s) throws CallbackException;

    public boolean onDelete(Session s) throws CallbackException;

    public void onLoad(Session s, Serializable id);

}

当对象实作Lifecycle接口时,会在save()update()delete()load()等方法执行之前呼叫对应的onSave() onUpdate()onDelete()onLoad(),其中onSave()onUpdate()onDelete()onLoad() 若传回true或丢出CallbackException,则对应的操作中止。

可以在实体对象定义时实作Validatable接口,其定义如下:

Validatable.java

package org.hibernate.classic;

 

public interface Validatable {

    public void validate() throws ValidationFailure;

}

如果定义时实作了Validatable接口,当对象被持久化之前会呼叫validate()方法,如果丢出ValidationFailure,则验证失败,对象的数据不会储存至数据库中。

132 Interceptor 界面

您可以在开启Session时加载一个自订Interceptor,这个Interceptor会在对应的动作发生之前呼叫对应的方法,方法是让您定义的Interceptor实作Interceptor接口,接口的定义如下:

Interceptor.java

package org.hibernate;

 

import java.io.Serializable;

import java.util.Iterator;

 

import org.hibernate.type.Type;

 

public interface Interceptor {

    // 加载对象之前执行

    public

      boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

      throws CallbackException;

 

    // flush 时,如果发现有Dirty data,则执行此方法

    public

      boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,

                          String[] propertyNames, Type[] types) throws CallbackException;

 

    // 储存对象前执行

    public

      boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

      throws CallbackException;

 

    // 删除对象前执行

    public

      void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

      throws CallbackException;

 

    // flush 前执行

    public void preFlush(Iterator entities) throws CallbackException;

 

    // flush 后执行

    public void postFlush(Iterator entities) throws CallbackException;

 

    // 判断传入的对象是否为 transient 状态

    public Boolean isTransient(Object entity);

 

    // flush 前呼叫这个方法判断 Dirty data

    // 传回Dirty data属性索引或null采预设行为

    public

      int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,

                      String[] propertyNames, Type[] types);

 

    // 手动建立实体对象,如果传回 null,则使用预设的建构方法建立实例

    public Object instantiate(String entityName, EntityMode entityMode, Serializable id)

      throws CallbackException;

 

    // 传回实体名称

    public String getEntityName(Object object) throws CallbackException;

 

    // 取得实体对象

    public Object getEntity(String entityName, Serializable id) throws CallbackException;

 

    // beginTransaction() 之后执行

    public void afterTransactionBegin(Transaction tx);

 

    // 在事务完成前执行

    public void beforeTransactionCompletion(Transaction tx);

 

    // 在事务完成后执行

    public void afterTransactionCompletion(Transaction tx);

}

假设您实作了SomeInterceptor类别:

SomeInterceptor.java

package onlyfun.caterpillar;

....

public class SomeInterceptor implements Interceptor {

    ....

}

在开启Session时,可以如下加载自订的Interceptor

SomeInterceptor someInterceptor = new SomeInterceptor();

Session session = sessionFactory.openSession(someInterceptor);

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值