`
zhangle
  • 浏览: 26600 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

使用Hibernate Interceptor和Annotation实现父子关系的计数

阅读更多
Hibernate中,对于domain model之间的父子关系,有时需要父对象需要得知子对象的数目,常规的做法是用一条sql语句"select count(*) ..."。

更好的做法可以借鉴RoR,在父对象中设置一个保存子对象数目的字段,添加删除的时候对这个字段进行更新。

但对这个字段进行更新的时候,往往需要显式的对父对象进行更新,比如create Topic时需要:
User user = this.userDao.get(userId);
Topic topic = new Topic(user);
this.topicDao.create(topic);
user.setTopicsCount(user.getTopicsCount() + 1);
this.userDao.update(user);


删除Topic的时候,则需要
User user = this.userDao.get(userId);
Topic topic = new Topic(user);
this.topicDao.create(topic);
user.setTopicsCount(user.getTopicsCount() - 1);
this.userDao.update(user);


有没有更好的方法呢?
我的做法是利用Hibernate Interceptor,在save和delete的时候自动的更新该字段。

下面是一个Annotation,标示了这个Annotation的自动将自动加减。
AutoCount.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoCount {

	Class clazz();

}


下面是一个例子,User和Topic是一对多的关系,User的topicsCount字段保存了Topic的数量,使用了AutoCount Annotation。
User.java
@Entity
@Table(name = "users")
public class User implements java.io.Serializable {
	@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
	private Set<Topic> topics;
       
	@AutoCount(clazz = Topic.class)
	@Column(name = "topics_count", nullable = false, columnDefinition = "default '0'")
	private Integer topicsCount;
}


Topic.java
@Entity
@Table(name = "topics")
public class Topic implements java.io.Serializable {
        
	@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = User.class)
	@JoinColumn(name = "user_id")
	private User user;
        
}


下面是Interceptor的实现
AutoCountInterceptor.java
public class AutoCountInterceptor extends EmptyInterceptor {

	protected final static String JAVASSIST_IDENTIFIER = "_$$_javassist";

	@Override
	public void onDelete(Object entity, Serializable id, Object[] state,
			String[] propertyNames, Type[] types) throws CallbackException {
		updateObjectCount(state, new CountOperation() {
			@Override
			public int execute(int count) {
				return count > 0 ? count - 1 : count;
			}
		});
	}

	@Override
	public boolean onSave(Object entity, Serializable id, Object[] state,
			String[] propertyNames, Type[] types) throws CallbackException {
		updateObjectCount(state, new CountOperation() {
			@Override
			public int execute(int count) {
				return count + 1;
			}
		});

		return false;
	}

	private void updateObjectCount(Object[] state, CountOperation operation) {
		for (int i = 0; i < state.length; i++) {
			if (state[i] != null) {
				FieldTarget fieldTarget = getAutoCountField(state[i]);

				if (fieldTarget != null) {
					Field field = fieldTarget.getField();

					Integer count = 0;
					try {
						Object target = fieldTarget.getTarget();
						field.setAccessible(true);
						count = operation.execute((Integer) field.get(target));
						field.set(target, count);

						state[i] = target;
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	private FieldTarget getAutoCountField(Object state) {
		Class clazz = state.getClass();

		if (clazz == null) {
			return null;
		}

		String s = clazz.getName();

		Class clazzToUse = null;
		Object target = null;

		FieldTarget fieldTarget = null;

		if (s.contains(JAVASSIST_IDENTIFIER)) {
			try {
				clazzToUse = Class.forName(s.substring(0, s
						.indexOf(JAVASSIST_IDENTIFIER)));

				Field[] ctFields = clazz.getDeclaredFields();
				ctFields[1].setAccessible(true);
				JavassistLazyInitializer javassistLazyInitializer = (JavassistLazyInitializer) ctFields[1]
						.get(state);
				target = javassistLazyInitializer.getImplementation();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				return null;
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
				return null;
			} catch (IllegalAccessException e) {
				e.printStackTrace();
				return null;
			}
		} else {
			clazzToUse = clazz;
			target = state;
		}
		
		if (clazzToUse != null) {
			Field[] fields = clazzToUse.getDeclaredFields();

			for (int i = 0; i < fields.length; i++) {
				if (fields[i].isAnnotationPresent(AutoCount.class)) {
					if (target != null) {
						try {
							Object value = getFieldValue(target, fields[i]
									.getName());
							if (value == null)
								return null;
							
							fields[i].setAccessible(true);
							fields[i].set(state, value);

							fieldTarget = new FieldTarget(fields[i], target);
						} catch (IllegalArgumentException e) {
							e.printStackTrace();
							return null;
						} catch (IllegalAccessException e) {
							e.printStackTrace();
							return null;
						}
					}

					return fieldTarget;
				}
			}
		}

		return null;
	}

	private interface CountOperation {
		int execute(int count);
	}

	private Object getFieldValue(Object target, String field) {
		Class clazz = target.getClass();
		try {
			Field f = clazz.getDeclaredField(field);
			f.setAccessible(true);
			return f.get(target);
		} catch (SecurityException e) {
			e.printStackTrace();
			return null;
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			return null;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			return null;
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			return null;
		}
	}

	private class FieldTarget {
		public FieldTarget(Field field, Object target) {
			super();
			this.field = field;
			this.target = target;
		}

		private Field field;

		public Field getField() {
			return field;
		}

		public Object getTarget() {
			return target;
		}

		private Object target;
	}
}


下面是一个测试用例
public class TestUserTopicsCountAdded extends AbstractTransactionalJUnit38SpringContextTests{

	private int userId = 10;
	
       @Resource(name="userDao")
	protected IUserDao userDao;
       
	@Rollback(true)
	public void testTopicCreatedAndCountAdded() {
		User user = this.userDao.get(userId);
		
		int expectedTopicsCount = user.getTopicsCount() + 1;
		
		Topic topic = new Topic(user);
		
		this.topicDao.create(topic);
		
		user = this.userDao.get(userId);
		int actualTopicsCount = user.getTopicsCount();
		
		Assert.assertEquals(expectedTopicsCount, actualTopicsCount);
	}
}


此外还需要修改spring的配置文件,在sessionFactory中加入
<property name="entityInterceptor" ref="autoInterceptor" />

这就可以实现在添加删除的时候,自动在父对象中更新对子对象的计数。

希望能够抛砖引玉,看大家是否有更好的方法。
分享到:
评论
6 楼 allwefantasy 2009-09-11  
其实也可以由更好的方案。命名约定 ---可以简化很多代码
5 楼 zhangle 2009-06-16  
修改了一下代码,可以支持lazy的fetch了。
hibernate使用了javassist来进行lazy加载,对此进行了特殊处理。
4 楼 zhangle 2009-06-16  
kjj 写道
我的疑惑在于,为获得一个count,搞出这么大队代码来,数据库层面完全可以用触发器解决这个问题吧,用hibernate来实现这个是不是有点太豪华了
!!!

不到300行的代码,不多吧。
用触发器的话,相关的表都要写相关的代码,多的话会产生大量重复的类似代码。DRY
3 楼 kjj 2009-06-16  
我的疑惑在于,为获得一个count,搞出这么大队代码来,数据库层面完全可以用触发器解决这个问题吧,用hibernate来实现这个是不是有点太豪华了
!!!
2 楼 打倒小日本 2009-06-16  
思路很好 感谢分享 学习学习
1 楼 zhangle 2009-06-16  
这个方法的局限性在于,FetchType需要设置为EAGER,设为LAZY的时候,利用反射机制得到的代理对象为null,不知道这个问题该怎么解决

相关推荐

    HibernateInterceptor.java

    Hibernate4,Interceptor,spring,quartz

    Struts2+Spring2.5+Hibernate3+annotation 整合程序

    4. 模板方法设计模式:如何使用Struts2的拦截器(Interceptor)实现通用逻辑,如权限验证和日志记录。 5. 故障排查:可能遇到的问题及解决办法,如类加载冲突、空指针异常等。 文件“lib3”可能是项目所需的库...

    struts2 hibernate3 spring2.5 annotation 整合

    它通过Action类处理业务逻辑,使用拦截器(Interceptor)来实现如日志、权限验证等功能,并将结果转发到JSP或其他视图组件。 Hibernate3则是一个对象关系映射(ORM)框架,用于处理数据库操作。它简化了Java应用与...

    Struts2 Spring Hibernate 框架整合 Annotation Maven project.zip

    总的来说,这个项目展示了如何使用Struts2、Spring和Hibernate框架,结合Annotation和Maven进行企业级Java Web应用的开发。通过合理配置和注解,可以有效地降低项目的复杂性,提高开发效率。同时,Maven的依赖管理和...

    Interceptor框架的实现

    本文将深入探讨Interceptor框架的实现,主要涉及Java代理和反射技术。 首先,理解Interceptor(拦截器)的概念。拦截器是一种设计模式,它允许我们在方法调用前后插入自定义的行为。在诸如日志记录、性能监控、事务...

    Hibernate与struts2结合使用的demo

    Hibernate是一个对象关系映射(ORM)框架,它简化了数据库操作,而Struts2则是一个基于MVC(Model-View-Controller)设计模式的行动驱动框架,用于构建结构清晰、可维护性强的Web应用。将这两者结合使用,可以构建出...

    struts2 interceptor annotation plugin

    而"struts2 interceptor annotation plugin"则是Struts2框架提供的一种使用注解来配置拦截器的方式,这种方式更加简洁、直观,减少了XML配置文件的复杂性。 注解(Annotation)是Java编程语言的一个重要特性,它...

    struts2+hibernate实现登录及增删改操作

    3. **登录功能实现**:在Struts2和Hibernate结合的项目中,登录功能通常涉及用户输入验证、数据库查询等步骤。首先,用户在前端界面输入用户名和密码,提交到Struts2的Action。Action中,通过Hibernate的Session查询...

    Struts2+Hibernate实现新闻发布系统

    总结来说,"Struts2+Hibernate实现新闻发布系统"涉及到的主要知识点包括Struts2的MVC架构、Action和拦截器,Hibernate的对象关系映射、CRUD操作,以及自定义的数据库设计。开发者需要理解这两个框架的基本原理和使用...

    利用反射和动态代理机制实现自定义拦截器Interceptor

    利用反射和动态代理机制实现自定义拦截器Interceptor 在本文中,我们将探讨如何利用反射和动态代理机制来实现自定义拦截器Interceptor。拦截器Interceptor是一种常见的设计模式,用于在方法调用前后执行某些操作,...

    ssh框架用struts2 hibernate实现图片的上传源码

    SSH框架,全称为Struts2、Spring和Hibernate的组合,是Java Web开发中常见的MVC架构。本项目涉及的核心知识点是使用Struts2处理表单提交,...实际项目开发时,还需要结合具体的业务需求和安全规范进行详细设计和实现。

    Struts2+Hibernate实现用户的增删改查

    在本项目中,Hibernate通过配置文件(hibernate.cfg.xml)设定数据库连接信息,实体类(User)映射数据库中的表,使用注解或XML映射文件描述字段对应关系。SessionFactory负责创建Session,Session是与数据库交互的...

    Springboot+redis+Interceptor+自定义annotation实现接口自动幂等

    1. 创建拦截器:使用Spring MVC的`HandlerInterceptor`接口,实现`preHandle`、`postHandle`和`afterCompletion`方法。 2. 注册拦截器:在Spring MVC配置中注册自定义的拦截器。 3. 检查幂等性:在`preHandle`方法中...

    EJB+Annotation实现AOP的DEMO

    这篇博客"使用EJB+Annotation实现AOP的DEMO"主要介绍了如何在EJB中利用注解(Annotation)来实现AOP的功能。在Java EE中,EJB 3.0及后续版本引入了大量的注解,使得开发者可以免去编写XML配置文件,直接在代码中声明...

    Struts2+Hibernate+MySQL实现登录

    Struts2、Hibernate和MySQL是三个在...这个项目为你提供了一个基础的登录系统实现,你可以在此基础上学习和扩展,例如添加注册功能、实现验证码、使用Ajax异步验证、加入权限控制等,以提升Web应用的安全性和用户体验。

    hibernate+struts2实现添删改查

    1. **对象关系映射(ORM)**:将Java对象和数据库表之间建立映射,使开发者可以使用面向对象的方式来操作数据库。 2. **Session**:作为持久化层的接口,负责管理对象的状态,提供了事务管理和缓存机制。 3. **...

    Struts2+hibernate实现登陆和增删改

    Struts2是一款基于MVC(Model-View-Controller)设计模式的开源框架,用于构建可维护性高、结构清晰的Web应用程序,而Hibernate则是一个对象关系映射(ORM)工具,它简化了数据库操作,将Java对象与数据库表进行映射...

    Struts+Hibernate实现用户登录案例

    Struts是MVC(Model-View-Controller)设计模式的一种实现,用于处理用户的请求和控制业务逻辑,而Hibernate则是一个对象关系映射(ORM)框架,用于简化数据库操作。 1. **Struts框架**: - **MVC模式**:Struts...

Global site tag (gtag.js) - Google Analytics