精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-09-27
参考文章:http://www.iteye.com/topic/477134
项目中要对数据的更新做审计,比如订单的每一项的变化,会员主要信息的变化等。
老版的程序是这样处理的,在更新前先查一次数据库,然后依次比对各列,得到修改变化的信息。但在我们新版程序中,由于用到了singleSesssion,那么在一个Session中不允许出现两个ID相同的对象,所以老路是走不通的。
随后到网上搜索,就找到了上面这篇文章,博主写得很好,按照这个思路我也把例子写出来了。但是后面还碰到了几个问题,在这里也分享一下,希望能对大家有帮助。
现有主要配置如下: <bean id="crudListener" class="com.liut.core.listener.CrudListener"> </bean>
按照例子程序可以输出log日志,说明监听器是起作用的。那么接下来要把日志信息写入数据库,在监听器中引入IAuditLogService审计日志服务接口,用来保存日志信息。新的配置如下: <bean id="crudListener" class="com.liut.core.listener.CrudListener"> <property name="auditLogService"><ref bean="auditLogService" /></property> </bean>
启动应用控制台报错,是因为这违反了spring的原则,auditLogService引用了sessionFactory,现在sessionFactory的监听器里要引用auditLogService,当然是不允许的。好吧换一种方式 /** * 初始化审计日志服务类 * @Title: init void * @throws */ private synchronized void init(){ if(auditLogService == null){ WebApplicationContext context =WebApplicationContextUtils.getWebApplicationContext(SessionAgentTool.getSession().getRealHttpSession().getServletContext()); auditLogService =(IAuditLogService)context.getBean("auditLogService"); } } (其中SessionAgentTool是工具类,可以得到当前session)
结果执行修改动作时,一点反应没有,没有记录日志,也没有错误。控制台的日志信息照常输出。这个汗。。。啊!经过数小时的修改、测试,终未果。静下心来分析整个过程,终于发现问题了,在PostUpdateEventListener中,调用保存日志的方法,这时PostUpdateEventListener还会被触发,这不成死循环了?
问题找到就好办了,现在重写保存的方法,用SQL绕过hibernate监听。OK,测试通过!
到这里还没有结束,刚才一直用码表来测试,现在来测试订单,MYGOD!Hibernate在执行flush时数据下标越界【Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0 <bean id="transInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor" > <property name="transactionManager"> <ref bean="transactionManager"/> </property> <property name="transactionAttributes"> <props> <prop key="query*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="select*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="stat*">PROPAGATION_SUPPORTS,readOnly</prop> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
隐约记得有个子事务的说法也没有用过,搜索一下,修改如下 <bean id="transInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor" >
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="query*">PROPAGATION_SUPPORTS,readOnly</prop>
<prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop>
<prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop>
<prop key="select*">PROPAGATION_SUPPORTS,readOnly</prop>
<prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>
<prop key="stat*">PROPAGATION_SUPPORTS,readOnly</prop>
<prop key="log">PROPAGATION_REQUIRES_NEW</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
注意红色部分是添加项,也就是SQL保存日志的方法名,让它启用子事务。重启测试,OK,万事大吉了!
后面针对需求做了简单配置,因为不是所有的表都需要审计,而且数据量也会超大的,那么可以做到想记哪个表就记哪个表。下面是主要代码片
监听器配置文件 <!-- 审计日志配置策略: 1.可用的关键字有:insertAllow,insertDeny,updateAllow,updateDeny,deleteAllow,deleteDeny 2.没有配置对象的策略,所有字段不记录 3.allow和deny都配置的按allow验证,并忽略deny 4.allow和deny都允许指定all关键字 5.多个字段用英文逗号隔开 --> <bean id="crudListener" class="com.liut.core.listener.CrudListener"> <property name="auditableEntitys"> <map> <entry key="Order"> <map> <entry key="insertAllow"> <value>ordNo</value> </entry> <entry key="updateAllow"> <value>all</value> </entry> <entry key="deleteAllow"> <value>ordNo</value> </entry> </map> </entry> <entry key="OrderDetail"> <map> <entry key="insertDeny"> <value>all</value> </entry> <entry key="updateAllow"> <value>all</value> </entry> <entry key="deleteDeny"> <value>all</value> </entry> </map> </entry> <entry key="User"> <map> <entry key="insertDeny"> <value>userName</value> </entry> <entry key="updateAllow"> <value>all</value> </entry> <entry key="deleteDeny"> <value>userName</value> </entry> </map> </entry> <entry key="UserInfo"> <map> <entry key="insertAllow"> <value>email</value> </entry> <entry key="updateAllow"> <value>all</value> </entry> <entry key="deleteAllow"> <value>email</value> </entry> </map> </entry> </map> </property> </bean>
sessionFactory配置文件
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"><ref bean="datasource"/></property> 。。。(省略部分配置) <property name="eventListeners"> <map> <entry key="post-insert"> <ref local="crudListener"/> </entry> <entry key="post-update"> <ref local="crudListener"/> </entry> <entry key="post-delete"> <ref local="crudListener"/> </entry> </map> </property> </bean>
CrudListener.java package com.liut.core.listener; 。。。(省略import信息) /** * Hibernate增删改监听器,记录审记日志 * <p>类名称:EntityCrudListener</p> * <p>类描述:post方法在数据更新后执行,pre方法在数据更新前执行 </p> * <p>创建人:LiuTong</p> * <p>创建时间:Sep 26, 2012 2:39:52 PM </p> * <p>修改人:LiuTong</p> * <p>修改时间:Sep 26, 2012 2:39:52 PM </p> * <p>修改备注: </p> * @version */ public class CrudListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener { private static final long serialVersionUID = 1L; private static final String INSERT = "INSERT"; private static final String UPDATE = "UPDATE"; private static final String DELETE = "DELETE"; /** * 允许或不允许全部时,指定all即可 */ public static final String ALL = "all"; private static final Log logger = LogFactory.getLog(CrudListener.class); /** * 配置审计对象的记录策略 */ private Map<String,Map<String,String>> auditableEntitys; /** * 审计日志服务类 */ private IAuditLogService auditLogService; @Override public void onPostInsert(PostInsertEvent event) { if (auditableEntitys.containsKey(event.getEntity().getClass().getSimpleName()) && event.getEntity() instanceof BaseEntity) { init(); // 保存 插入日志 AuditLog log = newAuditLog(); log.setTableName(event.getEntity().getClass().getSimpleName()); log.setDataId(((BaseEntity)event.getEntity()).getId().toString()); log.setDoType(INSERT); { Object[] state = event.getState(); String[] fields = event.getPersister().getPropertyNames(); String content = ""; if(state != null && fields != null && state.length == fields.length){ for(int i = 0 ; i < fields.length ; i ++){ if(isLog(event.getEntity(),fields[i],INSERT)){ content = addStr(null, state, fields, content, i); } } } log.setContent("[" + content + "]"); } logger.debug("插入审计日志 INSERT AuditLog "); insert(log); } } @Override public void onPostUpdate(PostUpdateEvent event) { if (auditableEntitys.containsKey(event.getEntity().getClass().getSimpleName()) && event.getEntity() instanceof BaseEntity) { init(); // 保存 修改日志 AuditLog log = newAuditLog(); log.setTableName(event.getEntity().getClass().getSimpleName()); log.setDataId(((BaseEntity)event.getEntity()).getId().toString()); log.setDoType(UPDATE); { Object[] oldState = event.getOldState(); Object[] newState = event.getState(); String[] fields = event.getPersister().getPropertyNames(); String content = ""; if(oldState != null && newState != null && fields != null && oldState.length == newState.length && oldState.length == fields.length){ for(int i = 0 ; i < fields.length ; i ++){ if(isLog(event.getEntity(),fields[i],UPDATE)){ if((newState[i] == null && oldState[i] != null) || (newState[i] != null && !newState[i].equals(oldState[i]) )){ content = addStr(oldState, newState, fields, content, i); } } } } log.setContent("[" + content + "]"); } logger.debug("插入审计日志 UPDATE AuditLog "); insert(log); } } @Override public void onPostDelete(PostDeleteEvent event) { if (auditableEntitys.containsKey(event.getEntity().getClass().getSimpleName()) && event.getEntity() instanceof BaseEntity) { init(); // 保存 删除日志 AuditLog log = newAuditLog(); log.setTableName(event.getEntity().getClass().getSimpleName()); log.setDataId(((BaseEntity)event.getEntity()).getId().toString()); log.setDoType(DELETE); { Object[] state = event.getDeletedState(); String[] fields = event.getPersister().getPropertyNames(); String content = ""; if(state != null && fields != null && state.length == fields.length){ for(int i = 0 ; i < fields.length ; i ++){ if(isLog(event.getEntity(),fields[i],DELETE)){ content = addStr(null, state, fields, content, i); } } } log.setContent("[" + content + "]"); } logger.debug("插入审计日志 DELETE AuditLog "); insert(log); } } /** * 记录审计日志 * @Title: insert * @param log void * @throws */ private void insert(AuditLog log) { auditLogService.log(log); } /** * 创建日志对象,同时设置操作人操作时间等信息 * @Title: newAuditLog * @return AuditLog * @throws */ private AuditLog newAuditLog() { Visit visit = SessionAgentTool.getCurrentVisit(); AuditLog log = new AuditLog(); log.setDoTime(TimeUtils.getCurrentStandardTime()); log.setEditorId(visit.getId()); log.setEditorName(visit.getUserOptionName()); return log; } /** * 验证策略是否允许记录日志,规则如下: * <ol> * <li>可用的关键字有:insertAllow,insertDeny,updateAllow,updateDeny,deleteAllow,deleteDeny</li> * <li>没有配置对象的策略,所有字段不记录</li> * <li>allow和deny都配置的按allow验证,并忽略deny</li> * <li>allow和deny都允许指定all关键字</li> * <li>多个字段用英文逗号隔开</li> * </ol> * @Title: isLog * @param entity * @param string * @param string2 * @return boolean * @throws */ private boolean isLog(Object entity, String field, String op) { Map<String,String> entityConfig = auditableEntitys.get(entity.getClass().getSimpleName()); if(entityConfig != null){ String allowFields = entityConfig.get(op.toLowerCase() + "Allow"); if(allowFields != null){ if(allowFields.equals(ALL) || containsField(allowFields,field)){ //配置ALL,所有允许 return true; } }else{ String denyFields = entityConfig.get(op.toLowerCase() + "Deny"); if(denyFields != null){ if(denyFields.equals(ALL) || containsField(denyFields,field)){ //配置ALL,所有不允许 return false; } } return true; } }else{ } //缺省不记录 return false; } /** * 配置中是否包含当前字段 * @Title: containsField * @param fields * @param field * @return boolean * @throws */ private boolean containsField(String fields, String field) { String[] fs = fields.split(","); for(String f : fs){ if(f.equals(field)){ return true; } } return false; } /** * 向content追加一个修改项 * @Title: addStr * @param oldState * @param newState * @param fields * @param content * @param i * @return String * @throws */ private String addStr(Object[] oldState, Object[] newState, String[] fields, String content, int i) { if(content.length() < 1000){ if(content.length() > 0){ content += ","; } content += "{columnName:\"" + fields[i] + "\",oldValue:\"" + (oldState == null ? "" : String.valueOf(oldState[i])) + "\",newValue:\"" + String.valueOf(newState[i]) + "\"}"; }else{ logger.warn("审计长度超过1000"); } return content; } /** * 初始化审计日志服务类 * @Title: init void * @throws */ private synchronized void init(){ if(auditLogService == null){ WebApplicationContext context =WebApplicationContextUtils.getWebApplicationContext(SessionAgentTool.getSession().getRealHttpSession().getServletContext()); auditLogService =(IAuditLogService)context.getBean("auditLogService"); } } public void setAuditableEntitys( Map<String, Map<String, String>> auditableEntitys) { this.auditableEntitys = auditableEntitys; } }
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-09-28
补充说明:在修改监听方法onPostUpdate(PostUpdateEvent event)中,event.getOldState()有时得不到值,保存时把update改为merge方法就可以正常得到。
同时也测试了onPreUpdate方法,单表更新时正常,但一个事务中有多个表时,用upadte更新仍然得不到。 |
|
返回顶楼 | |
发表时间:2012-10-01
刚好在研究这个业务,谢谢兄弟分享.支持码农分享事业
|
|
返回顶楼 | |
发表时间:2013-04-18
onpostupdate 保存碰到 “ERROR [org.hibernate.AssertionFailure] - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: collection [com.xinghuo.entity.organization.Department.children] was not processed by flush()” |
|
返回顶楼 | |
浏览 6042 次