Почему моя сущность не выселена из моего кеша второго уровня?
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. Управление кэшами.