论坛首页 编程语言技术论坛

hibernate3新特性EventListener完整实例

浏览 6042 次
精华帖 (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;
	}
}  

 

   发表时间:2012-09-28  
补充说明:在修改监听方法onPostUpdate(PostUpdateEvent event)中,event.getOldState()有时得不到值,保存时把update改为merge方法就可以正常得到。

同时也测试了onPreUpdate方法,单表更新时正常,但一个事务中有多个表时,用upadte更新仍然得不到。
0 请登录后投票
   发表时间:2012-10-01  
刚好在研究这个业务,谢谢兄弟分享.支持码农分享事业
0 请登录后投票
   发表时间: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()”
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics