`
bd_cool
  • 浏览: 60680 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

hibernate3新特性EventListener完整实例

阅读更多

参考文章: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;
	}
}  

 

分享到:
评论
6 楼 mlshenlong 2014-07-15  
您好!我的配置如下:
XML文件:
<bean id="historyListener" class="net.shopxx.listener.HistoryListener"></bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="eventListeners">
<map>
<entry key="post-insert">
<ref bean="historyListener" />
</entry>
<entry key="post-update">
<ref bean="historyListener" />
</entry>
<entry key="post-delete">
<ref bean="historyListener" />
</entry>
</map>
</property>
</bean>
监听类:
public class HistoryListener extends DefaultLoadEventListener implements PostInsertEventListener,
PostUpdateEventListener, PostDeleteEventListener {


private static final long serialVersionUID = -3142415669713724726L;



@Override
public void onPostDelete(PostDeleteEvent arg0) {
Object object=arg0.getEntity();
System.out.println(object);

}

@Override
@PostUpdate
public void onPostUpdate(PostUpdateEvent event) {
System.out.println(event.getEntity().getClass().getSimpleName()+":更新完毕");
for (int i = 0; i < event.getState().length; i++) {
// 更新前的值
Object oldValue = event.getOldState()[i];
// 更新后的新值
Object newValue = event.getState()[i];
//更新的属性名
String propertyName = event.getPersister().getPropertyNames()[i];

}

}

@Override
public void onPostInsert(PostInsertEvent arg0) {
Object object=arg0.getEntity();
System.out.println(object);

}

}
但是执行update的时候,老是不执行onPostUpdate方法,请问下问题出在哪里?谢谢!
5 楼 u012897256 2014-04-10  
有没有什么方法,可以在依然使用update的情况下,可以得到修改之前的记录呢?
4 楼 u012897256 2014-04-10  
但是,我把update改为merge方法后,就算修改的操作不成功,还是记录了;我想要的是只记录操作成功的日志,
3 楼 guijiaohu 2014-04-09  
能否提供一下其他相关类,我得qq是895225064,多谢啊。
2 楼 lzh166 2013-07-03  
SessionAgentTool 和 BaseEntity 类可否提供下。598620601@qq.com.
1 楼 bd_cool 2012-09-28  
补充说明:在修改监听方法onPostUpdate(PostUpdateEvent event)中,event.getOldState()有时得不到值,保存时把update改为merge方法就可以正常得到。

同时也测试了onPreUpdate方法,单表更新时正常,但一个事务中有多个表时,用upadte更新仍然得不到。

相关推荐

Global site tag (gtag.js) - Google Analytics