【问题标题】:Trying to use EhCache using Spring and a custom GenericDao interface that extends the Hibernate's JpaRepository尝试使用 EhCache 使用 Spring 和扩展 Hibernate 的 JpaRepository 的自定义 GenericDao 接口
【发布时间】: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;
    }

}

例如要管理FooBar这两个域名,你只需要创建如下两个接口:

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() 生成的缓存将被重置,因为将使用相同的缓存(即&lt;cache name="dao" /&gt;),而它应该不。


小径

我尝试实现自己的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?如果有,是哪一个?

【问题讨论】:

    标签: java spring hibernate ehcache genericdao


    【解决方案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 级别实现使缓存中的单个条目无效的方法?我很想知道实现是什么。
    猜你喜欢
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 2011-07-23
    • 2020-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多