【问题标题】:Why is my entity not evicted from my second-level cache?为什么我的实体没有从我的二级缓存中逐出?
【发布时间】:2016-06-08 23:45:30
【问题描述】:

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

@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 配置……

<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 注释会使用相同的密钥生成方案。

【问题讨论】:

    标签: spring hibernate ehcache second-level-cache evict


    【解决方案1】:

    我重写了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();
        }  
    }
    

    【讨论】:

    • 所以我理解你的回答,你是说我可以在我的应用程序中为每个 Cacheable 注释添加一个“密钥”(不是一个选项,因为我想要一个自动生成的密钥)或者我可以改变我的应用程序中每个方法的签名都带有 Cacheable(例如将 save(organizaiton org) 更改为 save(param1, param2, ...)?我理解正确吗?
    • 在我的应用程序中使用 Cacheable 注释重新编码每个方法签名不是一个选项——有数百个。给定我当前的方法签名和密钥生成器,我将如何编写 @CacheEvict 注释?
    • 我终于找到了一种方法来重写你的代码CodeKeyGenerator。请检查我在Updated solution Startsends 部分更新的答案
    • 好的,谢谢。也许这很明显,但是鉴于您的 CacheKeyGenerator,对应的 @CacheEvict 注释是什么?我是否也必须为其他我想将 CacheEvict 应用于方法的类更改此 CacheKeyGenerator?
    • 你可以使用你已经使用的相同的缓存驱逐注解。正如您在 spring 配置中提到的这个 cachekeygenerator,它只会在一个地方发生变化
    【解决方案2】:

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

    来自@Cacheable.key 的 JavaDocs:

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

    所以,@Cacheable(value = "main") public Organization findById(String id) 表示返回的对象(Organization 类型)将与键 id 一起缓存。

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


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

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

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

    【讨论】:

    • 因为我将它包含在我的应用程序上下文中(来自我的问题),'
    • 您的密钥生成器为这两种方法生成不同的密钥,这再次导致密钥不匹配,因此不会被驱逐。打开调试日志以验证这一点。您将看到 Spring 缓存日志,其中将显示您如何生成密钥的逻辑错误。
    • 我创建了一个sample application 来向您证明我的答案有效。您可以下载它并以mvn clean test 运行它以查看所有测试是否通过。那里有一个测试可以根据您的调用检查缓存状态。我建议您使用我的示例并将您的代码添加到其中,而无需先使用自定义密钥生成器。如果您不对工作样本进行任何更改,事情应该会奏效。然后插入您的密钥生成器以查看错误在哪里。
    • 当您说“您的密钥生成器为这两种方法生成不同的密钥”时,我知道,这就是重点。我不想为每个 @Cacheable 注释指定一个“键”。我喜欢能够自动生成密钥。鉴于此约束,我如何使用@CacheEvict 从我的缓存中逐出适当的实体?你明白我在问什么吗?
    • 缓存就像Map@Cacheable 就像 Map.put("key", "value")@CacheEvict 就像 Map.remove("key")。使用您的密钥生成器,您正在执行Map.put("key", "value")Map.remove("someotherkey")。如果您随后期望与"key" 对应的条目被驱逐,当然它不会被驱逐,因为您为putremove 使用了不同的密钥。您需要更改密钥生成器,以便将完全相同的密钥传递给 @Cacheable@CacheEvict
    【解决方案3】:

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

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

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

    在测试中如何获取m_cache 实例并不是特别清楚,但如果它确实是Hibernate 的二级缓存,则不会通过使用您使用的注解将其驱逐。

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

    sessionFactory.evict(Organization.class)
    

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

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

    【讨论】:

    • 这是一个分布式应用程序,每个应用程序服务器将使用一个 JVM,并将使用 Spring 和 ehcache 作为二级缓存。鉴于我的问题的限制,我将如何编写适当的 @CacheEvict 注释?解决方案应该适用于“org.springframework.cache.annotation.CacheEvict”注解。
    • 您能否发布您如何检索m_cache 实例的代码?目前,您的应用程序有 两个 缓存层。一个是 Spring Cache(缓存对 DAO 的调用),第二个是 Hibernate 的实际二级缓存。除非知道m_cache 是什么类型的缓存,否则很难做出任何评估。
    • 我已经在问题中发布了定义。 “m_cache”属于“net.sf.ehcache.Cache”类型。
    • 好的,但是如何获取该实例?在代码中,只有一个字段声明。我一直在说的一点是,如果它是 Hibernate 的缓存,它不会被 @CacheEvict 注释驱逐。
    • 我已经进一步编辑了我的问题。缓存取自 @Autowired net.sf.ehcache.CacheManager 实例。但是,您关注的是错误的细节。如果二级缓存配置不正确,我不会注册二级缓存命中,但我是。如果您对配置感到困惑,您需要做的就是告诉我将给我的密钥生成器提供什么适当的 CacheEvict 注释,这将是并且赏金是你的。
    猜你喜欢
    • 2012-12-16
    • 2016-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多