首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

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

2012-11-03 
使用Hibernate Interceptor和Annotation实现父子关系的计数Hibernate中,对于domain model之间的父子关系,

使用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";@Overridepublic void onDelete(Object entity, Serializable id, Object[] state,String[] propertyNames, Type[] types) throws CallbackException {updateObjectCount(state, new CountOperation() {@Overridepublic int execute(int count) {return count > 0 ? count - 1 : count;}});}@Overridepublic boolean onSave(Object entity, Serializable id, Object[] state,String[] propertyNames, Type[] types) throws CallbackException {updateObjectCount(state, new CountOperation() {@Overridepublic 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" />

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

希望能够抛砖引玉,看大家是否有更好的方法。 1 楼 zhangle 2009-06-16   这个方法的局限性在于,FetchType需要设置为EAGER,设为LAZY的时候,利用反射机制得到的代理对象为null,不知道这个问题该怎么解决 2 楼 打倒小日本 2009-06-16   思路很好 感谢分享 学习学习 3 楼 kjj 2009-06-16   我的疑惑在于,为获得一个count,搞出这么大队代码来,数据库层面完全可以用触发器解决这个问题吧,用hibernate来实现这个是不是有点太豪华了
!!! 4 楼 zhangle 2009-06-16   kjj 写道我的疑惑在于,为获得一个count,搞出这么大队代码来,数据库层面完全可以用触发器解决这个问题吧,用hibernate来实现这个是不是有点太豪华了
!!!
不到300行的代码,不多吧。
用触发器的话,相关的表都要写相关的代码,多的话会产生大量重复的类似代码。DRY 5 楼 zhangle 2009-06-16   修改了一下代码,可以支持lazy的fetch了。
hibernate使用了javassist来进行lazy加载,对此进行了特殊处理。 6 楼 allwefantasy 2009-09-11   其实也可以由更好的方案。命名约定 ---可以简化很多代码

热点排行