`
bd_cool
  • 浏览: 60214 次
  • 性别: 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更新仍然得不到。

相关推荐

    hibernate源码

    10. **EventListener和Interceptor**:允许自定义事件监听器和拦截器,实现特定的业务逻辑,如在数据持久化前后进行操作。 11. **JPA(Java Persistence API)集成**:Hibernate也可作为JPA的实现,提供标准的持久...

    hibernate-release-5.0.12.Final.rar

    Hibernate允许开发者通过实现特定接口(如Interceptor或EventListener)进行定制化操作,如在对象持久化前后执行自定义逻辑。 11. **HQL与JPQL** Hibernate Query Language(HQL)和Java Persistence Query ...

    hibernate_core_API

    7. **Event and Listener**:Hibernate的事件监听机制允许我们在特定操作(如save、update、delete等)前后执行自定义逻辑。通过实现Listener接口并注册,我们可以定制化Hibernate的行为。 8. **Type System**:...

    Hibernate

    11. **事件监听器**:Hibernate支持在特定操作(如保存、更新、删除等)前后触发自定义逻辑,通过实现EventListener接口,开发者可以扩展Hibernate的行为。 12. **类型系统**:Hibernate提供了丰富的类型系统,不仅...

    hibernate-orm.zip

    Hibernate具有良好的扩展性,如插件化的缓存实现(如Ehcache、Infinispan)、拦截器(Interceptor)和事件监听器(EventListener),使得开发者可以根据需求定制和扩展其功能。 10. 最佳实践: 在实际应用中,...

    hibernate api帮助文档.chm

    10. Hibernate事件和监听器:允许开发者在特定操作(如对象加载、保存、删除等)前后执行自定义逻辑,通过实现和注册EventListener子类。 11. Second Level Cache:Hibernate提供了二级缓存机制,可以提高数据访问...

    Hibernate实战

    另外,书中还会涉及 Hibenate 的扩展,如使用拦截器(Interceptor)进行自定义行为,或者利用事件监听器(EventListener)在特定操作前后执行额外逻辑。此外,Spring框架与Hibernate的整合也是常见的应用场景,这...

    java hibernate框架代码

    3. **持久化类(Persistent Class)**:Hibernate能够管理的类称为持久化类,它们的对象被称为持久化对象。这些对象可以在数据库中创建、读取、更新和删除,通过Session接口进行操作。 4. **SessionFactory和...

    hibernate中文参考文档

    - **事件监听器(Event Listener)**: 类似于拦截器,但更灵活,可以监听更多的事件类型。 - **自定义类型(Custom Types)**: 可以定义自己的数据类型,例如日期格式化等。 ### 总结 通过以上内容的介绍,我们了解到...

    hibernate-core-lib.zip

    3. SessionFactory接口:是Hibernate的核心工厂类,用于创建Session实例。一个SessionFactory代表了一个数据库连接池,它的生命周期通常与应用程序相同,创建后就不再改变。 4. Entity类和映射文件:Entity类是Java...

    hiberante ref document

    还包括CGLIB和JPA支持、拦截器(Interceptor)、事件监听器(Event Listener)等,这些都为开发者提供了更大的灵活性和定制能力。 通过学习并熟练掌握上述内容,开发者将能够充分利用Hibernate的强大功能,高效地...

    snaker源码

    对于使用 Hibernate 进行持久化管理的项目,Snaker-Hibernate 提供了与 Hibernate 的整合,使得流程实例、任务实例等对象可以无缝存取于 Hibernate 管理的数据库中。这种集成简化了数据库操作,让开发者可以专注于...

    Java设计模式

    - **Observer模式**:如Swing中的事件监听,以及`java.util.EventListener`接口。 单例模式的特点包括: - 只能有一个实例存在。 - 单例类必须自己创建并管理自己的唯一实例。 - 提供一个全局访问点来获取这个实例...

    Activiti工作流使用手册操作文档

    此外,Activiti支持自定义行为(如EventListener、TaskListener)和扩展点,以满足定制化需求。 总结来说,Activiti工作流是一个功能强大且灵活的流程管理工具,通过深入学习和实践本操作手册,Java开发者能够熟练...

    工作流学习笔记

    深入到源码层面,jBPM的工作流引擎实现了一系列核心概念,如流程定义(Process Definition)、流程实例(Process Instance)、任务(Task)和事件(Event)。开发者可能需要理解这些实体间的交互机制,例如,当一个...

    Spring 开发参考手册

    3. **数据访问**: 支持 JDBC、Hibernate、JPA 等多种数据访问技术。 4. **测试**: 提供了强大的支持工具,便于单元测试和集成测试。 5. **微服务架构**: 可以用于构建微服务架构中的服务发现、配置管理等功能。 ###...

    OSGi传说beta1.1.pdf

    public class EventListener { @EventHandler public void handleEvent(Event event) { // 事件处理逻辑 } } ``` #### 九、OSGi与Spring框架的整合 OSGi可以与Spring框架整合,利用Spring的强大功能进行依赖...

    会员管理系统 用java图形界面做的

    在Java中,这通常通过实现EventListener接口和对应的事件处理方法(如actionPerformed())来实现。 3. **MySQL数据库**:MySQL是一个流行的开源关系型数据库管理系统(RDBMS),适合于中小型应用程序的数据存储。在...

    java一个简单的即时通讯工具的设计与开发(源代码+论文)

    Java的EventObject、EventListener和EventDispatcher等接口可以构建这种模型。 7. **数据持久化**:为了保存用户信息和聊天记录,通常会涉及数据库操作。开发者需要了解如何使用Java的JDBC API与数据库交互,或者...

Global site tag (gtag.js) - Google Analytics