I'm trying to configure L2+QueryCache for Hibernate 6.1.7 in my Spring 6 app running Ehcache 3.10.x
In my app, Spring-level caches with method-level @Cacheable
, @CachePut
, @CacheEvict
, etc. work fine. I create the caches and they work as expected.
L2 cache also works correctly, but only if I don't create any L2 caches and let Spring create the default ones (missing_cache_strategy: create
). This is presentig heap-related problems in production because the default cache settings in Ehcache are allowing infinite heap usage.
How do I create L2 caches with my own configs, from code? I want to avoid the xml
Ehcache config at this time, and Ehcache seems to be moving away from it anyway.
This is how I configure Spring-level caches, and it works:
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
public CacheManager ehCacheManager() {
CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
//Create a Spring-level cache
var config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(SomeMethodArg.class, SomeMethodReturnType.class)
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(30))
.build();
cacheManager.createCache("my_spr_cache", Eh107Configuration.fromEhcacheCacheConfiguration(config));
return cacheManager;
}
}
Following the Ehcache docs and other sources, I was able to create L2 caches in a similar manner by adding another cache to the bean above:
//Create a L2 cache
var config = CacheConfigurationBuilder
.newCacheConfigurationBuilder(CacheKeyImplementation.class, AbstractReadWriteAccess.Item.class)
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMinutes(30))
.build();
//L2 cache name should be the fully qualified name of the Entity
cacheManager.createCache("com.mydomain.model.MyClass", Eh107Configuration.fromEhcacheCacheConfiguration(config));
And of course, the MyClass Entity is annotated with:
@jakarta.persistence.Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) //Note that some other classes instead use NONSTRICT_READ_WRITE
Now the cache I made is correctly created at runtime and Ehcache doesn't build a default one. I can confirm this from the startup logs - manually created caches are logged significantly earlier on.
Now the issue: this method is unreliable. Firstly, it seems that changing the ConcurrencyStrategy
breaks the cache creation code, for example, switching to NONSTRICT_READ_WRITE
on the entity will cause Exceptions:
Exception in thread "(3/16)" java.lang.ClassCastException: Invalid value type,
expected: org.hibernate.cache.spi.support.AbstractReadWriteAccess$Item
but was : org.hibernate.cache.spi.entry.StandardCacheEntryImpl
The first attempt I made was obviously following the exception literally and changing the value class to StandardCacheEntryImpl
. This just causes even more erratic behavior, with the cache behaving fine at low usage but failing when consistent load is applied:
Caused by: java.lang.ClassCastException: Invalid value type,
expected : org.hibernate.cache.spi.support.AbstractReadWriteAccess$Item
but was : org.hibernate.cache.spi.support.AbstractReadWriteAccess$SoftLockImpl
If I switch those two classes back and forth, I get the same exception but reversed.
This leads me to believe that I'm using the wrong approach entirely. I can't find any modern and updated question on this, could someone kindly point me in the right direction?
If it can be useful, this is the relevant config in my application.yml
:
# [More settings here...]
jpa:
# [More settings here...]
properties:
jakarta:
persistence:
sharedCache:
#required - enable selective caching mode - only entities with @Cacheable annotation will use L2 cache.
mode: ENABLE_SELECTIVE
hibernate:
cache:
use_second_level_cache: true
use_query_cache: true
region:
factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
jakarta:
cache:
missing_cache_strategy: create