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

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

и heres my Spring config...

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

Ответ 1

Я переписал CodeKeyGenerator, как показано ниже. Это сделает ключ на основе отправляемого параметра. Если это строка (в случае id), она будет использовать ее как есть. Если это объект 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();
    }  
}

Ответ 2

Вам не хватает ключей кеша для @Cacheable и @CacheEvict. Из-за этого в двух операциях используются разные ключи кеша, и, следовательно, объект не выдается.

Из JavaDocs для @Cacheable.key:

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")

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

Ответ 3

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

В соответствии с Hibernate docs кэш второго уровня представляет собой кеш кластера или уровня JVM (SessionFactory) на уровне класса, по-классу и по принципу сбора за подписью.

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

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

Вам нужно будет выдать его программно, например:

sessionFactory.evict(Organization.class)

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

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