Почему моя сущность не исключена из моего кэша второго уровня?

Я использую 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);
}

и вот мой весенний конфиг…

<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 будет использовать ту же схему генерации ключей.

3 ответа

Я переписал CodeKeyGenerator как ниже. Это создаст ключ на основе параметра, который вы отправите. Если это строка (в случае идентификатора), он будет использовать ее как есть. Если это Organization объект, он получает идентификатор от этого объекта и использует его для ключа. Таким образом, вам не нужно переписывать свой код во всех местах. (Единственное изменение - вам нужно заменить ваш 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 а также @CacheEvict, Из-за этого две операции используют разные ключи кеша и, следовательно, сущность не исключается.

Из JavaDocs для @Cacheable.key:

Выражение Spring Expression Language (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")

Это заставит две операции кэширования использовать один и тот же ключ.

То, что вы пытаетесь выселить, это не кэш второго уровня Hibernate, а скорее Spring Cache, который представляет собой совершенно другой уровень кэширования.

Согласно документам Hibernate, кэш второго уровня - это кластерный или JVM-уровень (SessionFactory-level) кэш-памяти для каждого класса в отдельности.

Это означает, что он управляется исключительно Hibernate и аннотациями, такими как @Cacheable или же @CacheEvict не имеют никакого влияния на это.

Не совсем понятно, как вы получаете m_cache экземпляра в вашем тесте, но при условии, что это действительно кэш второго уровня Hibernate, он не будет удален с помощью использованных вами аннотаций.

Вам придется выселить его программно, например:

sessionFactory.evict(Organization.class)

В любом случае, до тех пор, пока вы выполняете весь доступ к данным в пределах одной JVM и через Hibernate, вам не нужно беспокоиться об удалении кэша, он прозрачно обрабатывается самой платформой.

Подробнее о возможностях выселения смотрите в документации по Hibernate, глава 20.3. Управление кешами.

Другие вопросы по тегам