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
For cache keys Hibernate
may potentially use three different implementations (please check org.hibernate.cache.spi.CacheKeysFactory
interface and it's createCollectionKey
, createEntityKey
and createNaturalIdKey
methods), moreover, Hibernate
, allows to override default CacheKeysFactory
via hibernate.cache.keys_factory
setting, so, technically you have no chance to guess what cache key implementation will be used for particular cache region, moreover cache regions may contain different data, so, Object.class
is a valid definition of cache key implementation.
In case of cache values implementations may also differ:
Hibernate
stores entity identifiers thereMap
or StandardCacheEntryImpl
or implementation of org.hibernate.cache.spi.support.AbstractReadWriteAccess.Lockable
so, for cache values Object.class
is the only valid definition as well.