Context and question
I'm trying to configure EHCache with Hibernate in Spring Boot 2.2, but it seems I'm doing something wrong. I've looked at several tutorials and SO questions but didn't find something that matched fully my approach.
I chose the approach of a no-XML, jcache configuration for the caching.
However, Hibernate doesn't detect the existing cache manager (I checked and even enforced with @AutoconfigureBefore
: the cache manager is loaded before the Hibernate auto-configuration).
As a result, Hibernate creates a second EhcacheManager
and throws several warnings such as the following:
HHH90001006: Missing cache[com.example.demo.one.dto.MyModel] was created on-the-fly. The created cache will use a provider-specific default configuration: make sure you defined one. You can disable this warning by setting 'hibernate.javax.cache.missing_cache_strategy' to 'create'.
I tried to use a HibernatePropertiesCustomizer
to tell Hibernate which cache manager it should use.
The bean is instanciated, but never called, so it loses all its appeal and purpose.
Does somebody know what I'm doing wrong and how I should intimate Hibernate to use the cache manager I already configured rather than creating his own?
I compared my configuration with the one JHipster generates.
It looks very similar, though their HibernatePropertiesCustomizer
is called.
I did not succeed in identifying the difference between their cache configuration and mine.
Notes from later tests (edits)
This appears tobe related to my data source configuration (see code below).
I tried removing it and enabling my JPA configuration in a simpler way, and the HibernatePropertiesCustomizer
is indeed called as expected.
@SpringBootApplication
@EnableTransactionManagement
@EnableJpaRepositories("com.example.demo.one.repository")
public class DemoApplication {
Actually, having configured my datasources manually (because I need to handle two distinct datasources), I circumvent Spring Boot's DataSourceAutoConfiguration
, and its HibernateJpaAutoConfiguration
is not applied.
This autoconfiguration is the one that applies the HibernatePropertiesCustomizer
(rather, it calls HibernateJpaConfiguration
to do it).
However, I'm not sure how I should call this configuration to apply it.
Code samples
Dependencies
I use the following dependencies (I let spring-boot-starter-parent
set the versions):
- org.springframework.boot:spring-boot-starter-data-jpa
- org.springframework.boot:spring-boot-starter-cache
- org.hibernate:hibernate-jcache
- javax.cache:cache-api
- org.ehcache:ehcache
- org.projectlombok:lombok as a comfort
Cache config
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.jsr107.Eh107Configuration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.cache.CacheManager;
import java.time.Duration;
@Configuration
@EnableCaching
@Slf4j
//@AutoConfigureBefore(value = {DataSource1Config.class, DataSource2Config.class})
public class CacheConfiguration {
private static final int TIME_TO_LIVE_SECONDS = 240;
private static final int MAX_ELEMENTS_DEFAULT = 200;
// Create this configuration as a bean so that it is used to customize automatically created caches
@Bean
public javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration() {
final org.ehcache.config.CacheConfiguration<Object, Object> cacheConfiguration =
CacheConfigurationBuilder
.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(MAX_ELEMENTS_DEFAULT))
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(TIME_TO_LIVE_SECONDS)))
.build();
return Eh107Configuration.fromEhcacheCacheConfiguration(
cacheConfiguration
);
}
@Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) {
log.error(">>>>>>>>>>>> customizer setup"); // Printed
return hibernateProperties -> {
log.error(">>>>>>>>>>>> customizer called"); // Not printed
hibernateProperties.put("hibernate.javax.cache.cache_manager", cacheManager);
};
}
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer(javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration) {
return cm -> {
createCache(cm, com.example.demo.one.dto.MyModel.class.getName(), jcacheConfiguration);
};
}
private void createCache(CacheManager cm, String cacheName, javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration) {
javax.cache.Cache<Object, Object> cache = cm.getCache(cacheName);
if (cache != null) {
cm.destroyCache(cacheName);
}
cm.createCache(cacheName, jcacheConfiguration);
}
}
Data source configuration
I have two data sources.
The second one is similar to this one, minus the @Primary
annotations.
Removing the second datasource does not solve the issue.
package com.example.demo.config;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.demo.one.repository",
entityManagerFactoryRef = "dataSource1EntityManagerFactory",
transactionManagerRef = "transactionManager1"
)
public class DataSource1Config {
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource.one")
public DataSourceProperties dataSource1Properties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource dataSource1(DataSourceProperties dataSource1Properties) {
return dataSource1Properties.initializeDataSourceBuilder().build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource1) {
return builder
.dataSource(dataSource1)
.packages("com.example.demo.one.dto")
.build();
}
@Bean
@Primary
public PlatformTransactionManager transactionManager1(EntityManagerFactory dataSource1EntityManagerFactory) {
return new JpaTransactionManager(dataSource1EntityManagerFactory);
}
}
application.yml
spring:
jpa:
database: <my-db>
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: <my-dialect>
jdbc.time_zone: UTC
javax:
cache:
#missing_cache_strategy: fail # Useful for testing if Hibernate creates a second cache manager
cache:
use_second_level_cache: true
use_query_cache: false
region.factory_class: jcache