hibernate3新特性EventListener完整实例

来源:互联网 发布:服装设计网络课程 编辑:程序博客网 时间:2024/04/29 18:34

参考文章:http://www.iteye.com/topic/477134

 

  项目中要对数据的更新做审计,比如订单的每一项的变化,会员主要信息的变化等。

 

  老版的程序是这样处理的,在更新前先查一次数据库,然后依次比对各列,得到修改变化的信息。但在我们新版程序中,由于用到了singleSesssion,那么在一个Session中不允许出现两个ID相同的对象,所以老路是走不通的。

 

  随后到网上搜索,就找到了上面这篇文章,博主写得很好,按照这个思路我也把例子写出来了。但是后面还碰到了几个问题,在这里也分享一下,希望能对大家有帮助。

 

  现有主要配置如下:

Java代码  收藏代码
  1. <bean id="crudListener" class="com.liut.core.listener.CrudListener">  
  2. </bean>  

 

  按照例子程序可以输出log日志,说明监听器是起作用的。那么接下来要把日志信息写入数据库,在监听器中引入IAuditLogService审计日志服务接口,用来保存日志信息。新的配置如下:

Java代码  收藏代码
  1. <bean id="crudListener" class="com.liut.core.listener.CrudListener">  
  2.         <property name="auditLogService"><ref bean="auditLogService" /></property>  
  3. </bean>  

 

  启动应用控制台报错,是因为这违反了spring的原则,auditLogService引用了sessionFactory,现在sessionFactory的监听器里要引用auditLogService,当然是不允许的。好吧换一种方式

Java代码  收藏代码
  1. /** 
  2.      * 初始化审计日志服务类 
  3.      * @Title: init  void 
  4.      * @throws 
  5.      */  
  6.     private synchronized void init(){  
  7.         if(auditLogService == null){  
  8.             WebApplicationContext context =WebApplicationContextUtils.getWebApplicationContext(SessionAgentTool.getSession().getRealHttpSession().getServletContext());  
  9.             auditLogService =(IAuditLogService)context.getBean("auditLogService");  
  10.         }  
  11.     }  

 (其中SessionAgentTool是工具类,可以得到当前session)

 

  结果执行修改动作时,一点反应没有,没有记录日志,也没有错误。控制台的日志信息照常输出。这个汗。。。啊!经过数小时的修改、测试,终未果。静下心来分析整个过程,终于发现问题了,在PostUpdateEventListener中,调用保存日志的方法,这时PostUpdateEventListener还会被触发,这不成死循环了?

 

  问题找到就好办了,现在重写保存的方法,用SQL绕过hibernate监听。OK,测试通过!

 

  到这里还没有结束,刚才一直用码表来测试,现在来测试订单,MYGOD!Hibernate在执行flush时数据下标越界【Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
】,这怎么可能?后来发现了规律,只在在事务中间的对象记录日志就会有这个错误。难道是事务的问题???看看现在是这样配的:

Java代码  收藏代码
  1. <bean id="transInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor" >  
  2.         <property name="transactionManager">   
  3.             <ref bean="transactionManager"/>   
  4.         </property>   
  5.         <property name="transactionAttributes">  
  6.             <props>  
  7.                 <prop key="query*">PROPAGATION_SUPPORTS,readOnly</prop>  
  8.                 <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop>   
  9.                 <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop>  
  10.                 <prop key="select*">PROPAGATION_SUPPORTS,readOnly</prop>  
  11.                 <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>  
  12.                 <prop key="stat*">PROPAGATION_SUPPORTS,readOnly</prop>  
  13.                 <prop key="*">PROPAGATION_REQUIRED</prop>  
  14.             </props>  
  15.         </property>   
  16.     </bean>  

 

 

  隐约记得有个子事务的说法也没有用过,搜索一下,修改如下

Java代码  收藏代码
  1. <bean id="transInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor" >  
  2.         <property name="transactionManager">   
  3.             <ref bean="transactionManager"/>   
  4.         </property>   
  5.         <property name="transactionAttributes">  
  6.             <props>  
  7.                 <prop key="query*">PROPAGATION_SUPPORTS,readOnly</prop>  
  8.                 <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop>   
  9.                 <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop>  
  10.                 <prop key="select*">PROPAGATION_SUPPORTS,readOnly</prop>  
  11.                 <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>  
  12.                 <prop key="stat*">PROPAGATION_SUPPORTS,readOnly</prop>  
  13.                 <span style="color: #ff0000;"><prop key="log">PROPAGATION_REQUIRES_NEW</prop></span>  
  14.                 <prop key="*">PROPAGATION_REQUIRED</prop>  
  15.             </props>  
  16.         </property>   
  17.     </bean>  

 

  注意红色部分是添加项,也就是SQL保存日志的方法名,让它启用子事务。重启测试,OK,万事大吉了!

 

  后面针对需求做了简单配置,因为不是所有的表都需要审计,而且数据量也会超大的,那么可以做到想记哪个表就记哪个表。下面是主要代码片

 

监听器配置文件

Java代码  收藏代码
  1. <!--   
  2.     审计日志配置策略:  
  3.     1.可用的关键字有:insertAllow,insertDeny,updateAllow,updateDeny,deleteAllow,deleteDeny  
  4.     2.没有配置对象的策略,所有字段不记录  
  5.     3.allow和deny都配置的按allow验证,并忽略deny  
  6.     4.allow和deny都允许指定all关键字  
  7.     5.多个字段用英文逗号隔开  
  8.      -->  
  9.     <bean id="crudListener" class="com.liut.core.listener.CrudListener">  
  10.         <property name="auditableEntitys">  
  11.             <map>  
  12.                 <entry key="Order">  
  13.                     <map>  
  14.                         <entry key="insertAllow">  
  15.                             <value>ordNo</value>  
  16.                         </entry>  
  17.                         <entry key="updateAllow">  
  18.                             <value>all</value>  
  19.                         </entry>  
  20.                         <entry key="deleteAllow">  
  21.                             <value>ordNo</value>  
  22.                         </entry>  
  23.                     </map>  
  24.                 </entry>  
  25.                 <entry key="OrderDetail">  
  26.                     <map>  
  27.                         <entry key="insertDeny">  
  28.                             <value>all</value>  
  29.                         </entry>  
  30.                         <entry key="updateAllow">  
  31.                             <value>all</value>  
  32.                         </entry>  
  33.                         <entry key="deleteDeny">  
  34.                             <value>all</value>  
  35.                         </entry>  
  36.                     </map>  
  37.                 </entry>  
  38.                 <entry key="User">  
  39.                     <map>  
  40.                         <entry key="insertDeny">  
  41.                             <value>userName</value>  
  42.                         </entry>  
  43.                         <entry key="updateAllow">  
  44.                             <value>all</value>  
  45.                         </entry>  
  46.                         <entry key="deleteDeny">  
  47.                             <value>userName</value>  
  48.                         </entry>  
  49.                     </map>  
  50.                 </entry>  
  51.                 <entry key="UserInfo">  
  52.                     <map>  
  53.                         <entry key="insertAllow">  
  54.                             <value>email</value>  
  55.                         </entry>  
  56.                         <entry key="updateAllow">  
  57.                             <value>all</value>  
  58.                         </entry>  
  59.                         <entry key="deleteAllow">  
  60.                             <value>email</value>  
  61.                         </entry>  
  62.                     </map>  
  63.                 </entry>  
  64.             </map>  
  65.         </property>  
  66.     </bean>  

 

 

sessionFactory配置文件

 

Java代码  收藏代码
  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
  2.         <property name="dataSource"><ref bean="datasource"/></property>  
  3.   
  4. 。。。(省略部分配置)  
  5.         <property name="eventListeners">  
  6.             <map>  
  7.                 <entry key="post-insert">  
  8.                     <ref local="crudListener"/>  
  9.                 </entry>  
  10.                 <entry key="post-update">  
  11.                     <ref local="crudListener"/>  
  12.                 </entry>  
  13.                 <entry key="post-delete">  
  14.                     <ref local="crudListener"/>  
  15.                 </entry>  
  16.             </map>  
  17.         </property>  
  18.     </bean>  

 

CrudListener.java

Java代码  收藏代码
  1. package com.liut.core.listener;  
  2. 。。。(省略import信息)  
  3. /** 
  4.  * Hibernate增删改监听器,记录审记日志 
  5.  * <p>类名称:EntityCrudListener</p> 
  6.  * <p>类描述:post方法在数据更新后执行,pre方法在数据更新前执行 </p> 
  7.  * <p>创建人:LiuTong</p> 
  8.  * <p>创建时间:Sep 26, 2012 2:39:52 PM </p> 
  9.  * <p>修改人:LiuTong</p> 
  10.  * <p>修改时间:Sep 26, 2012 2:39:52 PM </p> 
  11.  * <p>修改备注:   </p> 
  12.  * @version 
  13.  */  
  14. public class CrudListener implements PostInsertEventListener,    
  15.         PostUpdateEventListener, PostDeleteEventListener {  
  16.     private static final long serialVersionUID = 1L;  
  17.       
  18.     private static final String INSERT = "INSERT";  
  19.       
  20.     private static final String UPDATE = "UPDATE";  
  21.       
  22.     private static final String DELETE = "DELETE";  
  23.       
  24.     /** 
  25.      * 允许或不允许全部时,指定all即可 
  26.      */  
  27.     public static final String ALL = "all";  
  28.       
  29.     private static final Log logger = LogFactory.getLog(CrudListener.class);  
  30.       
  31.     /** 
  32.      * 配置审计对象的记录策略 
  33.      */  
  34.     private Map<String,Map<String,String>> auditableEntitys;  
  35.       
  36.     /** 
  37.      * 审计日志服务类 
  38.      */  
  39.     private IAuditLogService auditLogService;  
  40.       
  41.     @Override    
  42.     public void onPostInsert(PostInsertEvent event) {    
  43.         if (auditableEntitys.containsKey(event.getEntity().getClass().getSimpleName())  
  44.                 && event.getEntity() instanceof BaseEntity) {  
  45.             init();  
  46.             //  保存 插入日志  
  47.             AuditLog log = newAuditLog();  
  48.             log.setTableName(event.getEntity().getClass().getSimpleName());  
  49.             log.setDataId(((BaseEntity)event.getEntity()).getId().toString());  
  50.             log.setDoType(INSERT);  
  51.             {  
  52.                 Object[] state = event.getState();  
  53.                 String[] fields = event.getPersister().getPropertyNames();  
  54.                 String content = "";  
  55.                 if(state != null && fields != null  
  56.                         && state.length == fields.length){  
  57.                     for(int i = 0 ; i < fields.length ; i ++){  
  58.                         if(isLog(event.getEntity(),fields[i],INSERT)){  
  59.                             content = addStr(null, state, fields,  
  60.                                     content, i);  
  61.                         }  
  62.                     }  
  63.                 }  
  64.                 log.setContent("[" + content + "]");  
  65.             }  
  66.             logger.debug("插入审计日志 INSERT AuditLog ");  
  67.             insert(log);  
  68.         }  
  69.     }    
  70.     
  71.     @Override    
  72.     public void onPostUpdate(PostUpdateEvent event) {   
  73.         if (auditableEntitys.containsKey(event.getEntity().getClass().getSimpleName())  
  74.                 && event.getEntity() instanceof BaseEntity) {  
  75.             init();  
  76.             // 保存 修改日志   
  77.             AuditLog log = newAuditLog();  
  78.             log.setTableName(event.getEntity().getClass().getSimpleName());  
  79.             log.setDataId(((BaseEntity)event.getEntity()).getId().toString());  
  80.             log.setDoType(UPDATE);  
  81.             {  
  82.                 Object[] oldState = event.getOldState();  
  83.                 Object[] newState = event.getState();  
  84.                 String[] fields = event.getPersister().getPropertyNames();  
  85.                 String content = "";  
  86.                 if(oldState != null && newState != null && fields != null  
  87.                         && oldState.length == newState.length && oldState.length == fields.length){  
  88.                     for(int i = 0 ; i < fields.length ; i ++){  
  89.                         if(isLog(event.getEntity(),fields[i],UPDATE)){  
  90.                             if((newState[i] == null && oldState[i] != null)  
  91.                                     || (newState[i] != null && !newState[i].equals(oldState[i]) )){  
  92.                                 content = addStr(oldState, newState, fields,  
  93.                                         content, i);  
  94.                             }  
  95.                         }  
  96.                     }  
  97.                 }  
  98.                 log.setContent("[" + content + "]");  
  99.             }  
  100.             logger.debug("插入审计日志 UPDATE AuditLog ");  
  101.             insert(log);  
  102.         }    
  103.     }  
  104.   
  105.     @Override    
  106.     public void onPostDelete(PostDeleteEvent event) {    
  107.         if (auditableEntitys.containsKey(event.getEntity().getClass().getSimpleName())  
  108.                 && event.getEntity() instanceof BaseEntity) {  
  109.             init();  
  110.             // 保存 删除日志  
  111.             AuditLog log = newAuditLog();  
  112.             log.setTableName(event.getEntity().getClass().getSimpleName());  
  113.             log.setDataId(((BaseEntity)event.getEntity()).getId().toString());  
  114.             log.setDoType(DELETE);  
  115.             {  
  116.                 Object[] state = event.getDeletedState();  
  117.                 String[] fields = event.getPersister().getPropertyNames();  
  118.                 String content = "";  
  119.                 if(state != null && fields != null  
  120.                         && state.length == fields.length){  
  121.                     for(int i = 0 ; i < fields.length ; i ++){  
  122.                         if(isLog(event.getEntity(),fields[i],DELETE)){  
  123.                             content = addStr(null, state, fields,  
  124.                                     content, i);  
  125.                         }  
  126.                     }  
  127.                 }  
  128.                 log.setContent("[" + content + "]");  
  129.             }  
  130.             logger.debug("插入审计日志 DELETE AuditLog ");  
  131.             insert(log);  
  132.         }    
  133.     }  
  134.       
  135.     /** 
  136.      * 记录审计日志 
  137.      * @Title: insert  
  138.      * @param log void 
  139.      * @throws 
  140.      */  
  141.     private void insert(AuditLog log) {  
  142.         auditLogService.log(log);  
  143.     }  
  144.   
  145.     /** 
  146.      * 创建日志对象,同时设置操作人操作时间等信息 
  147.      * @Title: newAuditLog  
  148.      * @return AuditLog 
  149.      * @throws 
  150.      */  
  151.     private AuditLog newAuditLog() {  
  152.         Visit visit = SessionAgentTool.getCurrentVisit();  
  153.         AuditLog log = new AuditLog();  
  154.         log.setDoTime(TimeUtils.getCurrentStandardTime());  
  155.         log.setEditorId(visit.getId());  
  156.         log.setEditorName(visit.getUserOptionName());  
  157.         return log;  
  158.     }  
  159.       
  160.     /** 
  161.      * 验证策略是否允许记录日志,规则如下: 
  162.      * <ol> 
  163.      * <li>可用的关键字有:insertAllow,insertDeny,updateAllow,updateDeny,deleteAllow,deleteDeny</li> 
  164.      * <li>没有配置对象的策略,所有字段不记录</li> 
  165.      * <li>allow和deny都配置的按allow验证,并忽略deny</li> 
  166.      * <li>allow和deny都允许指定all关键字</li> 
  167.      * <li>多个字段用英文逗号隔开</li> 
  168.      * </ol> 
  169.      * @Title: isLog  
  170.      * @param entity 
  171.      * @param string 
  172.      * @param string2 
  173.      * @return boolean 
  174.      * @throws 
  175.      */  
  176.     private boolean isLog(Object entity, String field, String op) {  
  177.         Map<String,String> entityConfig = auditableEntitys.get(entity.getClass().getSimpleName());  
  178.         if(entityConfig != null){  
  179.             String allowFields = entityConfig.get(op.toLowerCase() + "Allow");  
  180.             if(allowFields != null){  
  181.                 if(allowFields.equals(ALL)  
  182.                         || containsField(allowFields,field)){  
  183.                     //配置ALL,所有允许  
  184.                     return true;  
  185.                 }  
  186.             }else{  
  187.                 String denyFields = entityConfig.get(op.toLowerCase() + "Deny");  
  188.                 if(denyFields != null){  
  189.                     if(denyFields.equals(ALL)  
  190.                             || containsField(denyFields,field)){  
  191.                         //配置ALL,所有不允许  
  192.                         return false;  
  193.                     }  
  194.                 }  
  195.                 return true;  
  196.             }  
  197.         }else{  
  198.         }  
  199.         //缺省不记录  
  200.         return false;  
  201.     }  
  202.   
  203.     /** 
  204.      * 配置中是否包含当前字段 
  205.      * @Title: containsField  
  206.      * @param fields 
  207.      * @param field 
  208.      * @return boolean 
  209.      * @throws 
  210.      */  
  211.     private boolean containsField(String fields, String field) {  
  212.         String[] fs = fields.split(",");  
  213.         for(String f : fs){  
  214.             if(f.equals(field)){  
  215.                 return true;  
  216.             }  
  217.         }  
  218.         return false;  
  219.     }  
  220.       
  221.     /** 
  222.      * 向content追加一个修改项 
  223.      * @Title: addStr  
  224.      * @param oldState 
  225.      * @param newState 
  226.      * @param fields 
  227.      * @param content 
  228.      * @param i 
  229.      * @return String 
  230.      * @throws 
  231.      */  
  232.     private String addStr(Object[] oldState, Object[] newState,  
  233.             String[] fields, String content, int i) {  
  234.         if(content.length() < 1000){  
  235.             if(content.length() > 0){  
  236.                 content += ",";  
  237.             }  
  238.             content += "{columnName:\"" + fields[i] +   
  239.                 "\",oldValue:\"" + (oldState == null ? "" : String.valueOf(oldState[i])) +   
  240.                 "\",newValue:\"" + String.valueOf(newState[i]) + "\"}";  
  241.         }else{  
  242.             logger.warn("审计长度超过1000");  
  243.         }  
  244.         return content;  
  245.     }    
  246.   
  247.     /** 
  248.      * 初始化审计日志服务类 
  249.      * @Title: init  void 
  250.      * @throws 
  251.      */  
  252.     private synchronized void init(){  
  253.         if(auditLogService == null){  
  254.             WebApplicationContext context =WebApplicationContextUtils.getWebApplicationContext(SessionAgentTool.getSession().getRealHttpSession().getServletContext());  
  255.             auditLogService =(IAuditLogService)context.getBean("auditLogService");  
  256.         }  
  257.     }  
  258.   
  259.     public void setAuditableEntitys(  
  260.             Map<String, Map<String, String>> auditableEntitys) {  
  261.         this.auditableEntitys = auditableEntitys;  
  262.     }  
  263. }    
0 0