I have a Spring Boot app with Spring Data JPA (hibernate backend) repository classes. I've added a couple custom finder methods, some with specific @Query
annotation to tell it how to get the data. I have already set up EhCache for the hibernate 2nd level cache, but so far, the only way I can get these results caching is to enable the hibernate query cache. I'd prefer to define a specific cache and store the actual domain objects there just as if it were a normal finder. Below is my repo code:
public interface PromotionServiceXrefRepository extends PagingAndSortingRepository<PromotionServiceXref, Integer> {
@Query("SELECT psx FROM Customer c " +
"JOIN c.customerProductPromotions cpp " +
"JOIN cpp.productPromotion pp " +
"JOIN pp.promotion p JOIN p.promotionServiceXrefs psx " +
"WHERE c.customerId = ?1")
@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region = "promotionServiceXrefByCustomerId")
Set<PromotionServiceXref> findByCustomerId(int customerId);
}
And here is the "promotionServiceXrefByCustomerId" cache I defined, that is NOT being used:
<cache name="promotionServiceXrefByCustomerId" overflowToDisk="true" diskPersistent="true"
maxEntriesLocalHeap="3000000" eternal="true" diskSpoolBufferSizeMB="20" memoryStoreEvictionPolicy="LFU"
transactionalMode="off" statistics="true">
</cache>
What am I doing wrong? If I enable StandardQueryCache
then this data gets cached there and hibernate does not execute a query. But when I disable the query caching, this does not get cached. What am I doing wrong here? PLEASE HELP!
The reason the code you have is not working is that @Cache
is not intended to work that way. If you want to cache the results of a query method execution, the easiest way is to use Spring's caching abstraction .
interface PromotionServiceXrefRepository extends PagingAndSortingRepository<PromotionServiceXref, Integer> {
@Query("…")
@Cacheable("servicesByCustomerId")
Set<PromotionServiceXref> findByCustomerId(int customerId);
@Override
@CacheEvict(value = "servicesByCustomerId", key = "#p0.customer.id")
<S extends PromotionServiceXref> S save(S service);
}
This setup will cause results of calls to findByCustomerId(…)
be cached by the customer identifier. Note, that we added an @CacheEvict
to the overridden save(…)
method, so that the cache we populate with the query method is evicted, whenever an entity is saved. This probably has to be propagated to the delete(…)
methods as well.
Now you can go ahead an configure a dedicated CacheManager
(see the reference documentation for details) to plug in whichever caching solution you prefer (using a plain ConcurrentHashMap
here).
@Configuration
@EnableCaching
class CachingConfig {
@Bean
CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("servicesByCustomerId)));
return cacheManager;
}
}
您需要注意,通过放弃 Hibernate QueryCache,您有责任使在保存、更新、删除影响查询结果的实体时失效的查询失效(Oliver 通过在保存时设置 CacheEvict 所做的) - 我认为可能会很痛苦 - 或者至少你需要考虑并忽略它,如果它不是你的场景真正的问题。
First I quote your question:
What am I doing wrong?
The way you are trying to name the cache is not appropriate to how hibernate will use it. Check org.hibernate.engine.spi.CacheInitiator
which uses org.hibernate.internal.CacheImpl
which is based on:
if ( settings.isQueryCacheEnabled() ) {
final TimestampsRegion timestampsRegion = regionFactory.buildTimestampsRegion(
qualifyRegionName( UpdateTimestampsCache.REGION_NAME ),
sessionFactory.getProperties()
);
updateTimestampsCache = new UpdateTimestampsCache( sessionFactory, timestampsRegion );
...
}
And UpdateTimestampsCache.REGION_NAME
(equals to org.hibernate.cache.spi.UpdateTimestampsCache
) is what you are missing as the cache name . For the query cache you'll have to use exactly that cache name and no other!
Now few other thoughts related to your problem:
@Cache
and setting cache name to org.hibernate.cache.spi.UpdateTimestampsCache
will allow your query to be cached with ehcache by hibernate (spring cache abstraction is not involved here)Below is the configuration from one of my projects where ehcache + @Query + @QueryHints work as expected ( ehcache/ehcache-in-memory.xml
file):
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="in-memory" xmlns="http://ehcache.org/ehcache.xsd">
<!--<diskStore path="java.io.tmpdir"/>-->
<!--
30d = 3600×24×30 = 2592000
-->
<cache name="org.hibernate.cache.internal.StandardQueryCache"
maxElementsInMemory="9999" eternal="false"
timeToIdleSeconds="2592000" timeToLiveSeconds="2592000"
overflowToDisk="false" overflowToOffHeap="false"/>
<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
maxElementsInMemory="9999" eternal="true"
overflowToDisk="false" overflowToOffHeap="false"/>
<defaultCache maxElementsInMemory="9999" eternal="false"
timeToIdleSeconds="2592000" timeToLiveSeconds="2592000"
overflowToDisk="false" overflowToOffHeap="false"/>
</ehcache>
and hibernate.properties:
hibernate.jdbc.batch_size=20
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.validator.autoregister_listeners=false
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.hbm2ddl.auto=update
net.sf.ehcache.configurationResourceName=ehcache/ehcache-in-memory.xml
hibernate.dialect=org.hibernate.dialect.H2Dialect
and some versions from pom.xml for which my explanation applies:
<springframework.version>5.0.6.RELEASE</springframework.version>
<spring-security.version>5.0.5.RELEASE</spring-security.version>
<spring-data-jpa.version>2.1.0.RELEASE</spring-data-jpa.version>
<hibernate.version>5.2.13.Final</hibernate.version>
<jackson-datatype-hibernate5.version>2.9.4</jackson-datatype-hibernate5.version>
And the full working test is image.persistence.repositories.ImageRepositoryTest.java found here: https://github.com/adrhc/photos-server/tree/how-to-cache-results-of-a-spring-data-jpa-query-method-without-using-query-cache
Yep, run mvn clean install
or change my env.sh
if you really want to use my shell scripts. Check then the number of sql queries on behalf of 3x imageRepository.count()
call.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.