尝试使用 EhCache 使用 Spring 和扩展 Hibernate 的 JpaRepository 的自定义 GenericDao 接口
Posted
技术标签:
【中文标题】尝试使用 EhCache 使用 Spring 和扩展 Hibernate 的 JpaRepository 的自定义 GenericDao 接口【英文标题】:Trying to use EhCache using Spring and a custom GenericDao interface that extends the Hibernate's JpaRepository 【发布时间】:2012-12-24 21:55:49 【问题描述】:背景
这是我的工作(简化)GenericDao
接口,由任何DomainDao
实现:
GenericDao.java
@NoRepositoryBean
public interface GenericDao<E extends Persistable<K>, K extends Serializable> extends JpaRepository<E, K>
public List<E> findAll();
public E persist(E entity);
GenericDaoImpl.java
public class GenericDaoImpl<E extends Persistable<K>, K extends Serializable> extends SimpleJpaRepository<E, K> implements GenericDao<E, K>
private final JpaEntityInformation<E, ?> entityInformation;
private final EntityManager em;
private final Class<E> type;
public GenericDaoImpl(JpaEntityInformation<E, ?> entityInformation, EntityManager em)
super(entityInformation, em);
this.entityInformation = entityInformation;
this.em = em;
this.type = entityInformation.getJavaType();
@Override
public List<E> findAll()
return super.findAll();
@Override
@Transactional
public E persist(E entity)
if (entityInformation.isNew(entity) || !EntityUtils.isPrimaryKeyGenerated(type) && !em.contains(entity))
em.persist(entity);
return entity;
例如要管理Foo
和Bar
这两个域名,你只需要创建如下两个接口:
FooDao.java
public interface FooDao extends GenericDao<Foo, Integer>
BarDao.java
public interface BarDao extends GenericDao<Bar, Integer>
Spring
的 @Autowired
注释将自动实例化具有良好实体和主键类型的 GenericDaoImpl
。
问题
我现在尝试在我的 DAO 上添加一个缓存进程,使用 EhCache 和 EhCache Spring Annotations 模型。
GenericDao.java
@NoRepositoryBean
public interface GenericDao<E extends Persistable<K>, K extends Serializable> extends JpaRepository<E, K>
@Cacheable(cacheName = "dao")
public List<E> findAll();
@TriggersRemove(cacheName = "dao")
public E persist(E entity);
applicationContext.xml
<ehcache:annotation-driven cache-manager="ehCacheManager" />
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
ehcache.xml
<cache name="dao"
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
memoryStoreEvictionPolicy="LFU" />
使用GenericDao
的问题在于缓存应该彼此独立地管理每个DomainDao
。例如,在当前配置下,如果我调用fooDao.findAll()
,然后调用barDao.persist(new Bar())
,fooDao.findAll()
生成的缓存将被重置,因为将使用相同的缓存(即<cache name="dao" />
),而它应该不。
小径
我尝试实现自己的CacheKeyGenerator
,这将考虑到调用DomainDao
的类型:
applicationContext.xml
<ehcache:annotation-driven cache-manager="ehCacheManager" default-cache-key-generator="daoCacheKeyGenerator" />
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
<bean id="daoCacheKeyGenerator" class="myapp.dao.support.DaoCacheKeyGenerator" />
DaoCacheKeyGenerator.java
public class DaoCacheKeyGenerator implements CacheKeyGenerator<DaoCacheKey>
@Override
public DaoCacheKey generateKey(MethodInvocation methodInvocation)
Class<?> clazz = methodInvocation.getThis().getClass().getInterfaces()[0];
Method method = methodInvocation.getMethod();
String methodName = method.getName();
Class<?>[] parameterClasses = method.getParameterTypes();
return new DaoCacheKey(clazz, methodName, parameterClasses);
@Override
public DaoCacheKey generateKey(Object... data)
return null;
DaoCacheKey.java
public class DaoCacheKey implements Serializable
private static final long serialVersionUID = 338466521373614710L;
private Class<?> clazz;
private String methodName;
private Class<?>[] parameterClasses;
public DaoCacheKey(Class<?> clazz, String methodName, Class<?>[] parameterClasses)
this.clazz = clazz;
this.methodName = methodName;
this.parameterClasses = parameterClasses;
@Override
public boolean equals(Object obj) // <-- breakpoint
if (obj instanceof DaoCacheKey)
DaoCacheKey other = (DaoCacheKey) obj;
if (clazz.equals(other.clazz))
// if @TriggersRemove, reset any cache generated by a find* method of the same DomainDao
boolean removeCache = !methodName.startsWith("find") && other.methodName.startsWith("find");
// if @Cacheable, check if the result has been previously cached
boolean getOrCreateCache = methodName.equals(other.methodName) && Arrays.deepEquals(parameterClasses, other.parameterClasses);
return removeCache || getOrCreateCache;
return false;
@Override
public int hashCode() // <-- breakpoint
return super.hashCode();
上述DaoCacheKey
的问题在于,equals
方法从未被调用(至少程序永远不会中断),但hashCode
却调用了,因此无法应用该算法。
问题
有人已经管理过这样的缓存吗?如果是怎么办?我的尝试是否相关?如果是,如何调用 equals
方法,而不是 hashCode
一个?通过扩展现有的CacheKeyGenerator
?如果有,是哪一个?
【问题讨论】:
【参考方案1】:这是我最终采用的可行解决方案。只有几个精度:我的域都实现了 Spring 的Persistable
接口。而且,由于我使用的是反射,我不确定缓存过程节省的时间会不会减少一点......
applicationContext.xml
<ehcache:annotation-driven cache-manager="ehCacheManager" default-cache-key-generator="daoCacheKeyGenerator" />
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
<bean id="daoCacheKeyGenerator" class="myapp.dao.support.cache.DaoCacheKeyGenerator" />
DaoCacheKeyGenerator.java(使用gentyref 库)
public class DaoCacheKeyGenerator implements CacheKeyGenerator<DaoCacheKey>
@SuppressWarnings("unchecked")
@Override
public DaoCacheKey generateKey(MethodInvocation methodInvocation)
Method method = methodInvocation.getMethod();
Class<? extends GenericDao<?, ?>> daoType = (Class<? extends GenericDao<?, ?>>) methodInvocation.getThis().getClass().getInterfaces()[0];
Class<? extends Persistable<?>> domainType = getDomainType(daoType);
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] parameters = methodInvocation.getArguments();
return new DaoCacheKey(domainType, methodName, parameterTypes, parameters);
@SuppressWarnings("unchecked")
private Class<? extends Persistable<?>> getDomainType(Class<?> daoType)
Type baseDaoType = GenericTypeReflector.getExactSuperType(daoType, GenericDao.class);
ParameterizedType parameterizedBaseDaoType = (ParameterizedType) baseDaoType;
return (Class<? extends Persistable<?>>) parameterizedBaseDaoType.getActualTypeArguments()[0];
@Override
public DaoCacheKey generateKey(Object... data)
return null;
DaoCacheKey.java
public class DaoCacheKey implements Serializable
private static final long serialVersionUID = 338466521373614710L;
private Class<? extends Persistable<?>> domainType;
private String methodName;
private Class<?>[] parameterTypes;
private Object[] parameters;
public DaoCacheKey(Class<? extends Persistable<?>> domainType, String methodName, Class<?>[] parameterTypes, Object[] parameters)
this.domainType = domainType;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameters = parameters;
public Class<? extends Persistable<?>> getDomainType()
return domainType;
@Override
public boolean equals(Object obj)
return this == obj || obj instanceof DaoCacheKey && hashCode() == obj.hashCode();
@Override
public int hashCode()
return Arrays.hashCode(new Object[] domainType, methodName, Arrays.asList(parameterTypes), Arrays.asList(parameters) );
ehcache.xml
<cache name="dao"
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
memoryStoreEvictionPolicy="LFU">
<cacheEventListenerFactory class="myapp.dao.support.cache.DaoCacheEventListenerFactory" />
</cache>
DaoCacheEventListenerFactory.java
public class DaoCacheEventListenerFactory extends CacheEventListenerFactory
@Override
public CacheEventListener createCacheEventListener(Properties properties)
return new DaoCacheEventListener();
DaoCacheEventListener.java
public class DaoCacheEventListener implements CacheEventListener
@SuppressWarnings("unchecked")
@Override
public void notifyElementRemoved(Ehcache cache, Element element) throws CacheException
DaoCacheKey daoCachekey = (DaoCacheKey) element.getKey();
List<Class<? extends Persistable<?>>> impacts = getOneToManyImpacts(daoCachekey.getDomainType());
for (DaoCacheKey daoCachedkey : (List<DaoCacheKey>) cache.getKeys())
if (impacts.contains(daoCachedkey.getDomainType()))
cache.remove(daoCachedkey);
@SuppressWarnings("unchecked")
private List<Class<? extends Persistable<?>>> getOneToManyImpacts(Class<? extends Persistable<?>> domainType)
List<Class<? extends Persistable<?>>> impacts = new ArrayList<Class<? extends Persistable<?>>>();
impacts.add(domainType);
for (Method method : domainType.getDeclaredMethods())
if (method.isAnnotationPresent(OneToMany.class))
ParameterizedType parameterizedType = (ParameterizedType) method.getGenericReturnType();
Class<? extends Persistable<?>> impactedDomainType = (Class<? extends Persistable<?>>) parameterizedType.getActualTypeArguments()[0];
if (!impacts.contains(impactedDomainType))
impacts.addAll(getOneToManyImpacts(impactedDomainType));
return impacts;
@Override
public void notifyElementPut(Ehcache cache, Element element) throws CacheException
@Override
public void notifyElementUpdated(Ehcache cache, Element element) throws CacheException
@Override
public void notifyElementExpired(Ehcache cache, Element element)
@Override
public void notifyElementEvicted(Ehcache cache, Element element)
@Override
public void notifyRemoveAll(Ehcache cache)
@Override
public void dispose()
@Override
public Object clone() throws CloneNotSupportedException
return super.clone();
希望对;)
有帮助
【讨论】:
您是否在应用程序中遇到过一种情况,您必须在 GenericDao 级别实现使缓存中的单个条目无效的方法?我很想知道实现是什么。以上是关于尝试使用 EhCache 使用 Spring 和扩展 Hibernate 的 JpaRepository 的自定义 GenericDao 接口的主要内容,如果未能解决你的问题,请参考以下文章
EHCache 配置 + Spring Boot:NoCacheRegionFactoryAvailableException