hibernate3学习笔记(二十三)|进阶特性(一)

1.悲观锁定:

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

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

悲观锁定通常透过系统或资料库本身的功能来实现,依赖系统或资料库本身提供的锁定机制,Hibernate即是如此,可以利用Query或Criteria的setLockMode()方法来设定要锁定的表或列(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也可以在使用Session的load()或是lock()时指定锁定模式以进行锁定。

另外还有三种加锁模式Hibernate内部自动对资料进行锁定,与资料库无关:

LockMode.WRITE:在insert或update时进行锁定,Hibernate会在save()方法时自动获得锁定。

LockMode.READ:在读取记录时Hibernate会自动获得锁定。

LockMode.NONE:没有锁定。

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

2.乐观锁定:

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

乐观锁定(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.javapackage 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                                     

注意标签必须出现在标签之後,接着您可以试着在资料库中新增资料,例如:

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者开启了一个session1Session session1 = sessionFactory.openSession();// 在这之後,马上有另一个使用者2开启了session2Session 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 0Hibernate:update user set version=?, name=?, age=? where id=? and version=?v1 v2 1 0Hibernate:update user set version=?, name=?, age=? where id=? and version=?16:11:43,187 ERROR AbstractFlushingEventListener:277 - Could not synchronize database state with sessionorg.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,您可以捕捉这个例外作善後处理,例如在处理中重新读取资料库中的资料,同时将目前的资料与资料库中的资料秀出来,让使用者有机会比对不一致的资料,以决定要变更的部份,或者您可以 设计程式自动读取新的资料,并比对真正要更新的资料,这一切可以在背景执行,而不用让您的使用者知道。

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

3.Lifecycle 介面、Validatable 介面:

可以在实体物件定义时实作Lifecycle介面,这个介面定义如下:

Lifecycle.javapackage 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.javapackage org.hibernate.classic;public interface Validatable {   public void validate() throws ValidationFailure;}

如果定义时实作了Validatable介面,当物件被持久化之前会呼叫validate()方法,如果丢出ValidationFailure,则验证失败,物件的资料不会储存至资料库中。

一直觉得人应该去旅行,在年轻的时候,

hibernate3学习笔记(二十三)|进阶特性(一)

相关文章:

你感兴趣的文章:

标签云: