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" />
这就可以实现在添加删除的时候,自动在父对象中更新对子对象的计数。
希望能够抛砖引玉,看大家是否有更好的方法。
分享到:
相关推荐
Hibernate4,Interceptor,spring,quartz
通过代理和反射实现一个Interceptor框架
用注解(annotation)方式配置struts2 (webwork) 中action的拦截器(interceptor)
主要介绍了Java的Hibernate框架中的Interceptor和Collection,Hibernate是Java的SSH三大web开发框架之一,需要的朋友可以参考下
利用反射和动态代理机制实现自定义拦截器Interceptor http://blog.csdn.net/alanchen520/article/details/34087833
本篇文章给大家介绍了使用springboot和拦截器、redis来优雅的实现接口幂等,对于幂等在实际的开发过程中是十分重要的,因为一个接口可能会被无数的客户端调用,如何保证其不影响后台的业务处理,如何保证其只影响...
使用和比较实体版本 设想 “ JPA投影”示例项目假定以下情形: 它是管理足球队(CRUD操作)生命周期的应用程序。 Football Team实体是非常大的实体图(您需要在这一图上发挥您的想象力!) 当足球队更改其数据并...
第23章 管理Session和实现对话 23.1 管理Session对象的生命周期 23.1.1 Session对象的生命周期与本地线程绑定 23.1.2 Session对象的生命周期与JTA事务绑定 23.2 实现对话 23.2.1 使用游离对象 23.2.2 ...
Struts2.3.6实现自定义拦截器Interceptor http://blog.csdn.net/alanchen520/article/details/34086699
包含postman的chrome插件,postman-interceptor插件,离线安装,即可使用
基于SpringBoot的资源请求验证(Aspectj和Interceptor两方式实现)附JWT验证token 前言 在项目中,我们需要对前端请求的资源进行验证,判断是否具有相应的权限。比如某写资源只有在登录之后才有请求权限。本章以...
postman interceptor
InterCeptor
Postman-Interceptor下载
详细介绍struts2 interceptor
Struts2_interceptor_和_filter区别
针对这些问题,结合网络教学系统的开发,提出了一种基于Annotation和拦截器实现访问控制的实现方法。Annotation是在JDK1.5之后的版本中出现的新的用于标注元数据的类型。Interceptor(拦截器)是Struts2的一个强有力的...
web调试最好用的插件,带匹配的interceptor插件,且有验证可用的安装文档
使用拦截器时按如下步骤进行: (1)定义实现Interceptor接口的拦截器类 (2)通过Session启用拦截器,或者通过Configuration启用全局拦截器请看示例代码