10

我正在使用 Hibernate 4.3.11.Final 和 Spring 3.2.11.RELEASE。我对为什么我的缓存驱逐不起作用感到困惑。我在我的 DAO 中设置了这个……</p>

@Override
@Caching(evict = { @CacheEvict("main") })
public Organization save(Organization organization)
{
    return (Organization) super.save(organization);
}

@Override
@Cacheable(value = "main")
public Organization findById(String id)
{
    return super.find(id);
}

这是我的 Spring 配置……</p>

<cache:annotation-driven key-generator="cacheKeyGenerator" />

<bean id="cacheKeyGenerator" class="org.mainco.subco.myproject.util.CacheKeyGenerator" />

<bean id="cacheManager"
    class="org.springframework.cache.ehcache.EhCacheCacheManager"
    p:cacheManager-ref="ehcache"/>

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
    p:configLocation="classpath:ehcache.xml"
    p:shared="true" />

<util:map id="jpaPropertyMap">
    <entry key="hibernate.show_sql" value="true" />
    <entry key="hibernate.dialect" value="org.mainco.subco.myproject.jpa.subcoMysql5Dialect" />
    <entry key="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory" />
    <entry key="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider" />
    <entry key="hibernate.cache.use_second_level_cache" value="true" />
    <entry key="hibernate.cache.use_query_cache" value="false" />
    <entry key="hibernate.generate_statistics" value="true" />
    <entry key="javax.persistence.sharedCache.mode" value="ENABLE_SELECTIVE" />
</util:map>

<bean id="sharedEntityManager"
    class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

然而在下面的测试中,我的实体没有从缓存中被驱逐,我知道这是因为“命中计数#3:”的行打印出“3”,而“命中计数#2:”的行打印出“2” ”。

private net.sf.ehcache.Cache m_cache

@Autowired 
private net.sf.ehcache.CacheManager ehCacheManager;

@Before
public void setup()
{
    m_cache = ehCacheManager.getCache("main");
    m_transactionTemplate = new TransactionTemplate(m_transactionManager);
}   // setup

...
@Test
public void testCacheEviction()
{
    final String orgId = m_testProps.getProperty("test.org.id");

    // Load the entity into the second-level cache
    m_transactionTemplate.execute((TransactionCallback<Void>) transactionStatus -> {            
        m_orgSvc.findById(orgId);
        return null;
    });

    final long hitCount = m_cache.getStatistics().getCacheHits();
    System.out.println("hit count #1:" + hitCount);
    m_transactionTemplate.execute((TransactionCallback<Void>) transactionStatus -> {            
        final Organization org = m_orgSvc.findById(orgId);
        System.out.println("hit count:" + m_cache.getStatistics().getCacheHits());
        org.setName("newName");
        m_orgSvc.save(org);
        return null;
    });

    // Reload the entity.  This should not incur a hit on the cache.
    m_transactionTemplate.execute((TransactionCallback<Void>) transactionStatus -> {
        System.out.println("hit count #2:" + m_cache.getStatistics().getCacheHits());
        final Organization newOrg = m_orgSvc.findById(orgId);
        System.out.println("hit count #3:" + m_cache.getStatistics().getCacheHits());
        return null;
    });

什么是允许我从二级缓存中逐出实体的正确配置?

编辑:我在应用程序上下文中引用的 CacheKeyGenerator 类定义如下

public class CacheKeyGenerator implements KeyGenerator 
{

    @Override
    public Object generate(final Object target, final Method method, 
      final Object... params) {

        final List<Object> key = new ArrayList<Object>();
        key.add(method.getDeclaringClass().getName());
        key.add(method.getName());

        for (final Object o : params) {
            key.add(o);
        }
        return key;
    }  
}

因此,我不必为我喜欢的每个 @Cacheable 注释定义一个“键”(更少的代码)。但是,我不知道这如何适用于 CacheEviction。我认为@CacheEvict 注释将使用相同的密钥生成方案。

4

3 回答 3

4

我重写了CodeKeyGenerator如下。这将根据您发送的参数生成一个密钥。如果它是一个字符串(如果是 id),它将按原样使用它。如果它是一个Organization对象,它会从该对象获取 id 并将其用作键。这样你就不需要在所有地方重写你的代码。(唯一的改变是你需要CacheKeyGenerator用下面的代码替换你的。)

public class CacheKeyGenerator implements KeyGenerator 
{
    @Override
    public Object generate(final Object target, final Method method, 
      final Object... params) {
    StringBuilder sb = new StringBuilder();
    sb.append(o.getClass().getName());
    sb.append(method.getName());

    if (params[0].getClass().getName() == "Organization" ) {
      sb.append(((Organization) params[0]).id);
    }
    else if (params[0].getClass().getName() == "java.lang.String" ) {
      sb.append(params[0].toString());
    }
    return sb.toString();
    }  
}

于 2016-03-03T11:39:23.073 回答
3

您缺少 和 的缓存@Cacheable@CacheEvict。因此,这两个操作使用不同的缓存键,因此实体不会被驱逐。

来自 JavaDocs @Cacheable.key

用于动态计算密钥的 Spring 表达式语言 (SpEL) 表达式。默认为"",这意味着所有方法参数都被视为一个键,除非配置了自定义 {@link #keyGenerator}。

因此,@Cacheable(value = "main") public Organization findById(String id)意味着返回的对象(类型Organization)将与 key 一起缓存id

同样,@Caching(evict = { @CacheEvict("main") }) public Organization save(Organization organization)表示 的字符串表示organization将被视为缓存键。


解决方案是进行以下更改:

@Cacheable(value = "main", key ="#id)

@CacheEvict(value = "main", key = "#organization.id")

这将强制两个缓存操作使用相同的键。

于 2016-03-01T06:06:26.927 回答
1

您要驱逐的不是Hibernate 的二级缓存,而是Spring Cache,它是完全不同的缓存层。

根据 Hibernate 的文档,二级缓存是一个集群或 JVM 级(SessionFactory 级)缓存,基于类和集合

这意味着它仅由 Hibernate 和注释管理,例如@Cacheable@CacheEvict对其没有影响。

目前还不清楚如何m_cache在测试中获取实例,但如果它确实是 Hibernate 的二级缓存,则不会通过使用您使用的注释来驱逐它。

您必须以编程方式驱逐它,例如:

sessionFactory.evict(Organization.class)

无论如何,只要您在单个 JVM 中并通过 Hibernate 进行所有数据访问,您就不必担心缓存驱逐,它由框架本身透明地处理。

有关驱逐可能性的更多信息,请查看 Hibernate 文档,第20.3 章。管理缓存

于 2016-03-03T20:51:36.263 回答