浏览 4019 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-06-16
最后修改:2009-06-16
更好的做法可以借鉴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" /> 这就可以实现在添加删除的时候,自动在父对象中更新对子对象的计数。 希望能够抛砖引玉,看大家是否有更好的方法。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-06-16
这个方法的局限性在于,FetchType需要设置为EAGER,设为LAZY的时候,利用反射机制得到的代理对象为null,不知道这个问题该怎么解决
|
|
返回顶楼 | |
发表时间:2009-06-16
思路很好 感谢分享 学习学习
|
|
返回顶楼 | |
发表时间:2009-06-16
最后修改:2009-06-16
我的疑惑在于,为获得一个count,搞出这么大队代码来,数据库层面完全可以用触发器解决这个问题吧,用hibernate来实现这个是不是有点太豪华了
!!! |
|
返回顶楼 | |
发表时间:2009-06-16
kjj 写道 我的疑惑在于,为获得一个count,搞出这么大队代码来,数据库层面完全可以用触发器解决这个问题吧,用hibernate来实现这个是不是有点太豪华了
!!! 不到300行的代码,不多吧。 用触发器的话,相关的表都要写相关的代码,多的话会产生大量重复的类似代码。DRY |
|
返回顶楼 | |
发表时间:2009-06-16
修改了一下代码,可以支持lazy的fetch了。
hibernate使用了javassist来进行lazy加载,对此进行了特殊处理。 |
|
返回顶楼 | |
发表时间:2009-09-11
其实也可以由更好的方案。命名约定 ---可以简化很多代码
|
|
返回顶楼 | |