脏数据检查:


   什么是脏数据?脏数据并不是废弃和无用的数据,而是状态前后发生变化的数据。我们看下面的代码:


Transaction tx=session.beginTransaction(); 

 
 
  User user=(User)session.load(User.class,”1”);// 
 从数据库中加载符合条件的数据 

 
 
  user.setName(“zx”);// 
 改变了 
 user 
 对象的姓名属性,此时 
 user 
 对象成为了所谓的“脏数据” 

 
 
  tx.commit();


  当事务提交时, Hibernate 会对 session 中的 PO( 持久化对象 ) 进行检测,判断持久化对象的状态是否发生了改变,如果发生了改变就会将改变更新到数据库中。这里就存在一个问题, Hibernate 如何来判断一个实体对象的状态前后是否发生了变化。也就是说 Hibernate 是如何检查出一个数据已经变脏了。


通常脏数据的检查有如下两种办法:


 A 、数据对象监控:


数据对象监控是通过拦截器对数据对象的 setter 方法进行监控来实现的,这类似于数据库中的触发器的概念,当某一个对象的属性调用了 setter 方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。这个方法的优点是提高了数据更新的同步性,但是这也是它的缺点,如果一个实体对象有很多属性发生了改变,势必造成大量拦截器回调方法的调用,这些拦截器都是通过 Dynamic Proxy 或者 CGLIB 实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。


   B 、数据版本比对 :


这种方法是在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。这种方法降低了同步更新的实时性,但是当一个数据对象的很多属性发生改变时,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。


Hibernate 中是采用数据版本比对的方法来进行脏数据检查的,我们结合下面的代码来讲解 Hibernate 的具体实现策略。


Transaction tx=session.beginTransaction(); 

 
 
  User user=(User)session.load(User.class,”1”); 

 
 
  user.setName(“zx”);  

 
 
  tx.commit();


当调用 tx.commit(); 时好戏就此开场, commit() 方法会调用 session.flush() 方法,在调用 flush() 方法时,会首先调用 flushEverything() 来进行一些预处理(如调用 intercepter, 完成级联操作等),然后调用 flushEntities() 方法,这个方法是进行脏数据检查的关键。


在继续讲解之前,我要先来介绍一个内部数据结构 EntityEntry,EntityEntry 是从属于 SessionImpl(Session 接口的实现类 ) 的内部类,每一个 EntityEntry 保存了最近一次与数据库同步的实体原始状态信息(如:实体的版本信息,实体的加锁模式,实体的属性信息等)。除了 EntityEntry 结构之外,还存在一个结构,这个结构称为 EntityEntries ,它也是 SessionImpl 的内部类,而且是一个 Map 类型,它以 ”key-value” 的形式保存了所有与当前 session 实例相关联的实体对象和原始状态信息,其中 key 是实体对象, value EntityEntry 。而 flushEntities() 的工作就是遍历 entityEntities, 并将其中的实体对象与原始版本进行对比,判断实体对象是否发生来了改变。 flushEntities() 首先会判断实体的 ID 是否发生了改变,如果发生了改变则认为发生了异常,因为当前实体与 EntityEntry 的对应关系非法。如果没有发生异常,而且经过版本比对判断确实实体属性发生了改变,则向当前的更新任务队列中加入一个新的更新任务,此任务将在将在 session.flush() 方法中的 execute() 方法的调用中,转化为相应的 SQL 语句交由数据库去执行。最后 Transaction 将会调用当前 session 对应的 JDBC Connection commit() 方法将当前事务提交。


脏数据检查是发生在显示保存实体对象时,所谓显示保存是指在代码中明确使用 session 调用 save,update,saveOrupdate 方法对实体对象进行保存,如: session.save(user); 但是有时候由于级联操作的存在,会产生一个问题,比如当保存一个 user 对象时,会根据 user 对象的状态来对他所关联的 address 对象进行保存,但是此时并没有根据级联对象的显示保存语句。此时需要 Hibernate 能根据当前对象的状态来判断是否要将级联对象保存到数据库中。此时, Hibernate 会根据 unsaved-value 进行判断。 Hibernate 将首先取出目标对象的 ID ,然后将 ID unsaved-value 值进行比较,如果相等,则认为实体对象尚未保存,进而马上将进行保存,否则,则认为实体对象已经保存,而无须再次进行保存。比如,当向一个 user 对象新加入一个它所关联的 address 对象后,当进行 session.save(user) 时, Hibernate 会根据 unsaved-value 的值判断出哪个 address 对象需要保存,对于新加入的 address 对象它的 id 尚未赋值,以此为 null ,与 unsaved-value 值相等,因此 Hibernate 会将其视为未保存对象,生成 insert 语句加以保存。如果想使用 unsaved-value 必须如下配置 address 对象的 id 属性:


…… 

 
 
 <id name=”id” type=”java.lang.Integer” unsaved-value=”null”> 

 
 
  <generator class=”increment”/> 

 
 
 </id> 

 
 
 ……