首页 > 技术文章 > 拦截器

forerver-elf 2015-08-12 14:32 原文

当程序通过Hibernate来加载、保存、更新或删除对象时,会触发以下组件做出相应的处理:在数据库层,会引发触发器执行相关的操作;在Hibernate层,可以触发拦截器执行相关操作;在Hibernate层,可以触发事件处理系统执行相关操作。 

能激发触发器运行的事件可以分为以下几种:插入记录事件,即执行insert语句;更新记录事件,即执行update语句;删除记录事件,即执行delete语句。 

Session的save()、update()、saveOrUpdate()或delete()方法都会激发一个触发器,而这个触发器的行为会导致session的缓存的数据与数据库不一致,解决的办法是在执行完这个操作后,立即调用session的flush()个refresh()方法,迫使session的缓存与数据库同步。 

不管游离的对象的属性是否发生过变化,都会执行update语句,而update语句会激发数据库中相应的触发器。因此应该在映射文件的<class>元素中设置select-before-update属性,使它为true,这样不会盲目执行update语句。 

数据库触发器经常用来生成审计日志,缺点是不支持跨数据平台。Hibernate的拦截器也能生成审计日志,不依赖于具体的数据库平台。可以把拦截器看作是持久化层的触发器,当session执行save()、update()、saveOrUpdate()、delete()和refresh()方法时,就会调用拦截器的相关方法。用户定义的拦截器必须要实现org.hibernate.Interceptor接口,在这个接口中主要定义了如下方法: 
findDirty():决定session缓存中哪些对象是脏对戏,session的flush()方法调用根本方法,若返回null,session会按默认的方式进行脏检查。 
instantiate(Class clazz,Serializable id):创建实体类的实例。在session构造实体类的实例前调用本方法。若返回null,session会按默认的方式创建实体类的实例。 
isUnsaved(Object entity):session的saveOrUpdate()方法调用本方法;若返回true,则调用save()方法,若返回false,则调用update()方法;若返回null,就按默认的方式决定参数entity是临时对象还是游离对象。 
onDelete():当session删除一个对象之前调用此方法。 
onFlushDirty():当session的flush()方法执行玩所有SQL语句后调用次方法。 
onLoad():当session初始化一个持久化对象时调用本方法,若在这个方法中修改了持久化对象的数据,就返回true,否则返回false。 
onSave():当session保存一个对象之前调用本方法,若在这个方法中修改了持久化对象的数据,就返回true,否则返回false。 
postFlush(Iterator entities):当session的flush()方法执行完所有SQL语句后调用此方法。 
preFlush(Iterator entities):当session执行的flush()方法之前调用此方法。 

在org.hibernate包中还提供了Interceptor接口的一个实现类EmptyInterceptor。这个类的所有方法实际上什么也不做,用户自定义的拦截器类也可以扩展EmptyInterceptor类。 

审计系统主要包括一下接口和类: 
Auditable:凡是需要审计的持久化类都应该所实现Auditable接口。 
AuditLogRecord类:代表具体的审计日志。 
AuditLogInterceptor类:代表用于生成审计日志的拦截器。 
AuditLog类:提供用于生成审计日志的静态方法logEvent()。 

public interface Auditable{ 
   public Long getId;(); 


public class AuditLogRecord{ 
   public String message; 
   public Long entityId; 
   public Class entityClass; 
   public Date created; 
   
   public AuditLogRecord(String message,Long entityId,Class entityClass){ 
       this.message = message ; 
       this.entityId = entityId ; 
       this.entityClass = entityClass ; 
       this.created = new Date() ; 
   } 
   public AuditLogRecord(){} 


<hibernate-mapping> 
   <class name="AuditLogRecord"  table="AUDIT_LOGS"> 
       <id type="long" column="ID"> 
           <generator class="native"  /> 
       </id> 
       <property name="message" column="MESSAGE" access="field"  /> 
       ... 
</hibernate-mapping> 

为了使session保存或更新对象,能够激发拦截器,必须使interceptor与session关联。Interceptor对象有两种存放方式: 
SessionFactory.openSession(Interceptor):为每个session实例分配一个interceptor实例,这个实例存放在session范围内。 
Configuration.setInterceptor(Interceptor):为SessionFactory实例分配一个interceptor实例,这个实例存放在SessionFactory范围内,被所有的Session实例共享。 

Hibernate3的核心处理模块采用了“事件/监听器”设计模式。 

public Object get(String entityName, Serializable id) throws HibernateException{ 
   LoadEvent event = new LoadEvent(id, entityName, false, this) ; 
   boolean success = false ; 
   try{ 
      fileLoad(event, LoadEventListener.GET) ; 
      success = true ; 
      return event.getResult() ; 
   }finally{ 
      afterOperation(success); 
   } 

Session的get()方法触发org.hibernate.event.LoadEvent事件,该事件由org.hibernate.event.LoadEventListener接口的实现类来处理。 

在org.hibernate.event包中提供了各个监听器接口的默认实现类。此外,org.hibernate.event.def包中提供了各个监听器接口的默认实现类。 

批量处理数据是指在一个事务中处理大量数据。一般来说,应避免在应用层进行批量操作,而应该在数据层直接进行批量操作。 

在应用层主要有一下4种方式进行批量操作: 
通过Session来进行批量操作;通过StatelessSession来进行批量操作;通过HQL来进行批量操作;直接通过JDBC API进行批量操作。 

若通过一个session对象来出库大量持久化对象,应该及时从缓存中清空已经处理完毕并且不会在访问的对象。具体做法是在处理完一个对象或小批对象后,立刻调用flush()清理缓存,然后调用clear()清空缓存。 

通过session来进行批量处理会受到一下约束:需要在Hibernate的配置文件中设置JDBC单次批处理的数目,合理的取值通常为10~50;若对象采用identity标识符生成器,则Hibernate无法在JDBC层进行批量插入操作;进行批量处理时,建议关闭Hibernate的第二级缓存。 

批量插入: 
Session session = sessionFactory.openSession() ; 
Transaction tx = session.beginTransaction() ; 
for(int i = 0 ; i < 10000 ; i++ ){ 
    XX xx = new XX() ; 
    session.save(xx) ; 
    if( i % 20 == 0 ){ 
         session.flush() ; 
         session.clear() ; 
    } 

tx.commit(); 
session.close(); 

批量更新:使用可滚动的结果集org.hibernate.ScrollableResults,Query的scroll()方法返回一个ScrollableResults对象。 
Session session = sessionFactory.openSession() ; 
Transaction tx = session.beginTransaction() ; 
ScrollableResults xx = session.createQuery("from XX").scroll(ScrollMode.FORWARD_ONLY) ; 
int count = 0 ; 
while( xx.next() ){ 
    XX xx = (XX) xx.get(0) ; 
    xx.setXXX(abc); 
        if( i % 20 == 0 ){ 
         session.flush() ; 
         session.clear() ; 
    } 

tx.commit(); 
session.close(); 
Query的scroll()方法返回的ScrollableResults对象中实际并不包含任何对象,它仅包含用于在线定位数据库中对象表记录的游标。 

Session具有一个用于保持内存中对象与数据库中相应数据保持同步的缓存,位于Session缓存中的对象为持久化对象。但在进行批量操作时,把大量对象存放在Session缓存中会消耗大量内存空间。作为一种替代方案,可采用无状态的StatelessSession来进行批量处理。
通过StatelessSession进行批量更新: 
SatelessSession session = sessionFactory.openStatelessSession() ; 
Transaction tx = session.beginTransaction() ; 
ScrollableResults xx = session.createQuery("from XX").scroll(ScrollMode.FORWARD_ONLY) ; 
int count = 0 ; 
while( xx.next() ){ 
    XX xx = (XX) xx.get(0) ; 
    xx.setXXX(abc); 
        if( i % 20 == 0 ){ 
         session.flush() ; 
         session.clear() ; 
    } 

tx.commit(); 
session.close(); 

StatelessSession和Session有以下区别: 
StatelessSession没有缓存。通过StatelessSession来加载、保存或更新后的对象都处于游离状态。 
StatelessSession不会与Hibernate的第二级缓存交互。 
当调用StatelessSession的save()、update()或delete()方法时,会立即执行相应的SQL,而不会执行一条语句。 
StatelessSession不会对所加载的对象自动进行脏检查。 
StatelessSession不会对关联的对象进行任何级联操作。 
StatelessSession所做的操作可以被Interceptor拦截器捕获到,但会被Hibernate的事件处理系统忽略。 
通过一个StatelessSession对象两次加载OID为1的对象时,会得到两个不同的内存地址的对象。 

利用HQL进行批量操作:直接在数据库中完成,不会占用内存空间 
Session session = sessionFactory.openSession() ; 
Transaction tx = session.beginTransaction() ; 
String hql = "update X x set x.xx =: newXx where x.yy = : oldYy" ; 
int hqlEntities = session.createQuery(hql).setStrng("a","b") 
                                          .setStrng("c","d") 
                                          .executeUpdate(); 
tx.commit(); 
session.close(); 
                                  
Hibernate需要通过对象-关系映射文件中的元数据来了解域模型中的持久化类及其属性的类型。Hibernate提供了ClassMetadata和CollectionMetaData这两个接口来访问元数据。这两个接口位于org.hibernate.metadata中。SessionFactory的getClassMetadata()和getCollectionMeta()方法分别返回这两个接口的实例。 
X x = new X(); 
ClassMetadata xMeta = sessionFactory.getClassMetaData(X.class); 
Object[] propertyValues = xMeta.getPropertyValues(x); 
String[] propertyNames = xMeta.getPropertyNames(); 
Type[] propertyTypes = xMeta.getPropertyTypes(); 

聚集关系:在域模型中,有些类由几个部分类组成,部分类的对象的生命周期依赖于整理类对象的生命周期,当整体消失时,部分也随之小时。这种整体与部分的关系被称为聚集关系。 
<component name=""  class=""> 
  <parent name="" /> 
  <property name=""  type="" column=""  /> 
  ... 
</component> 
<component>元素表明该属性是类的父类的一个部分。 

值类(value)和实体类(entity)的区别:值类型没有OID,并不能被单独的实体化,它的生命周期依赖于所属的持久化类的对象的生命周期。实体类型有OID,可以被单独持久化。 

推荐阅读