使用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);
User user = this.userDao.get(userId);Topic topic = new Topic(user);this.topicDao.create(topic);user.setTopicsCount(user.getTopicsCount() - 1);this.userDao.update(user);
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface AutoCount {Class clazz();}
@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;}
@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; }
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);}}