hibernate - 使用 Spring Boot 2.1+ 为 Hibernate 配置缓存

标签 hibernate spring-boot jcache ehcache-3 spring-boot-2

背景和问题

我正在尝试在 Spring Boot 2.2 中使用 Hibernate 配置 EHCache,但似乎我做错了什么。
我查看了几个教程和 SO 问题,但没有找到与我的方法完全匹配的内容。

我为缓存选择了无 XML、jcache 配置的方法。
然而,Hibernate 并没有检测到现有的缓存管理器(我检查甚至强制使用 @AutoconfigureBefore :缓存管理器在 Hibernate 自动配置之前加载)。
结果,Hibernate 创建了第二个 EhcacheManager并抛出几个警告,如下所示:

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'.

我尝试使用 HibernatePropertiesCustomizer告诉 Hibernate 它应该使用哪个缓存管理器。
bean 被实例化,但从未被调用,因此它失去了所有的吸引力和目的。

有人知道我做错了什么以及我应该如何让 Hibernate 使用我已经配置的缓存管理器而不是创建自己的缓存管理器?

我将我的配置与 JHipster 进行了比较生成。
它看起来非常相似,尽管它们的 HibernatePropertiesCustomizer叫做。
我没有成功识别出他们的缓存配置和我的区别。

后期测试的笔记(编辑)

这似乎与我的数据源配置有关(请参见下面的代码)。
我尝试删除它并以更简单的方式启用我的 JPA 配置,而 HibernatePropertiesCustomizer确实按预期调用。

@SpringBootApplication
@EnableTransactionManagement
@EnableJpaRepositories("com.example.demo.one.repository")
public class DemoApplication {

实际上,手动配置了我的数据源(因为我需要处理两个不同的数据源),我绕过了 Spring Boot 的 DataSourceAutoConfiguration , 和 它的HibernateJpaAutoConfiguration未应用 .
此自动配置是应用 HibernatePropertiesCustomizer 的自动配置。 (相反,它调用 HibernateJpaConfiguration 来执行此操作)。
但是,我不确定应该如何调用此配置来应用它。

代码示例

依赖项

我使用以下依赖项(我让 spring-boot-starter-parent 设置版本):
  • 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 作为一种安慰

  • 缓存配置

    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);
        }
    }
    

    数据源配置

    我有两个数据源。
    第二个和这个类似,减去 @Primary注释。
    删除第二个数据源并不能解决问题。

    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);
        }
    }
    

    应用程序.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
    

    最佳答案

    这并不容易,但我找到了原因和解决方案。

    原因

    基本上,问题出在我配置 LocalContainerEntityManagerFactoryBean 的事实。我。

    如果你不这样做,Spring Boot 将使用它的 AutoConfigurations 来创建一切都很好,包括供应商属性(您在 spring.jpa.properties 下的所有内容)、 hibernate 属性(在 spring.jpa.hibernate 下的所有内容)以及应用默认值和自定义项,其中我期待已久的 HibernateJpaAutoConfiguration .

    但是因为我需要有几个数据源,所以我绕过了所有这些,听了我的教程,我做了懒惰的跟随。

        @Bean
        @Primary
        public LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource1) {
            return builder
                .dataSource(dataSource1)
                .packages("com.example.demo.one.dto")
                .build();
        }
    

    解决方案

    简而言之

    解决方案几乎很简单:做所有 Spring Boot 会做的事情。
    仅“几乎”,因为大多数这些机制都依赖于 AutoConfigurations(覆盖这些是代码异味,所以这不是这样做的方法)和/或内部/ protected 类(您不能直接调用)。

    可能的脆性?

    这意味着您基本上必须将 Spring Boot 的代码复制到自己的代码中,这可能会对 Spring Boot 的 future 升级产生一些脆弱性 (或者只是您的代码不会从最新的错误/性能修复中受益)。
    从这方面来看,我不是我在这里提出的解决方案的忠实拥护者。

    详细指南

    你依赖的 bean

    您需要将以下 bean 注入(inject)到您的数据源配置中:
  • org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties
  • org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
  • List<org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer>

  • 要执行的操作

    借鉴 org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration , 我加了一个 hibernate.resource.beans.container属性定制器。
    但是,我跳过了在我们的项目中不是问题的命名策略。

    这给了我以下构造函数和方法:

    
        public DataSource1Config(
            JpaProperties jpaProperties,
            HibernateProperties hibernateProperties,
            ConfigurableListableBeanFactory beanFactory,
            ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers
        ) {
            this.jpaProperties = jpaProperties;
            this.hibernateProperties = hibernateProperties;
            this.hibernatePropertiesCustomizers = determineHibernatePropertiesCustomizers(
                beanFactory,
                hibernatePropertiesCustomizers.orderedStream().collect(Collectors.toList())
            );
        }
    
        private List<HibernatePropertiesCustomizer> determineHibernatePropertiesCustomizers(
            ConfigurableListableBeanFactory beanFactory,
            List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers
        ) {
            List<HibernatePropertiesCustomizer> customizers = new ArrayList<>();
            if (ClassUtils.isPresent("org.hibernate.resource.beans.container.spi.BeanContainer",
                getClass().getClassLoader())) {
                customizers.add((properties) -> properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)));
            }
            customizers.addAll(hibernatePropertiesCustomizers);
            return customizers;
        }
    

    然后,利用 org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration ,我加载了供应商属性。
    在这里,我再次跳过了一些您可以查看的自动定制(JpaBaseConfiguration#customizeVendorProperties(Map) 及其在子类中的实现)。

        private Map<String, Object> getVendorProperties() {
            return new LinkedHashMap<>(
                this.hibernateProperties
                    .determineHibernateProperties(jpaProperties.getProperties(),
                        new HibernateSettings()
                            // Spring Boot's HibernateDefaultDdlAutoProvider is not available here
                            .hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers)
                    )
            );
        }
    

    完整的配置类

    作为引用,一旦我应用了上面详述的更改,我就会为您提供完整的配置类。

    package com.example.demo.config;
    
    import org.hibernate.cfg.AvailableSettings;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
    import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
    import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
    import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
    import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
    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.hibernate5.SpringBeanContainer;
    import org.springframework.orm.jpa.JpaTransactionManager;
    import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    import org.springframework.util.ClassUtils;
    
    import javax.persistence.EntityManagerFactory;
    import javax.sql.DataSource;
    import java.util.ArrayList;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
        basePackages = "com.example.demo.one.repository",
        entityManagerFactoryRef = "dataSource1EntityManagerFactory",
        transactionManagerRef = "TransactionManager1"
    )
    public class DataSource1Config {
    
        private final JpaProperties jpaProperties;
        private final HibernateProperties hibernateProperties;
        private final List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers;
    
        public DataSource1Config(
            JpaProperties jpaProperties,
            HibernateProperties hibernateProperties,
            ConfigurableListableBeanFactory beanFactory,
            ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers
        ) {
            this.jpaProperties = jpaProperties;
            this.hibernateProperties = hibernateProperties;
            this.hibernatePropertiesCustomizers = determineHibernatePropertiesCustomizers(
                beanFactory,
                hibernatePropertiesCustomizers.orderedStream().collect(Collectors.toList())
            );
        }
    
        private List<HibernatePropertiesCustomizer> determineHibernatePropertiesCustomizers(
            ConfigurableListableBeanFactory beanFactory,
            List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers
        ) {
            List<HibernatePropertiesCustomizer> customizers = new ArrayList<>();
            if (ClassUtils.isPresent("org.hibernate.resource.beans.container.spi.BeanContainer",
                getClass().getClassLoader())) {
                customizers.add((properties) -> properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)));
            }
            customizers.addAll(hibernatePropertiesCustomizers);
            return customizers;
        }
    
        @Bean
        @Primary
        @ConfigurationProperties(prefix = "datasource.lib")
        public DataSourceProperties dataSource1Properties() {
            return new DataSourceProperties();
        }
    
        @Bean
        @Primary
        public DataSource dataSource1(DataSourceProperties dataSource1Properties) {
            return dataSource1Properties.initializeDataSourceBuilder().build();
        }
    
        @Bean
        @Primary
        public LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory(EntityManagerFactoryBuilder factoryBuilder, DataSource dataSource1) {
            final Map<String, Object> vendorProperties = getVendorProperties();
    
            return factoryBuilder
                .dataSource(dataSource1)
                .packages("com.example.demo.one.dto")
                .properties(vendorProperties)
                .build();
        }
    
        @Bean
        @Primary
        public PlatformTransactionManager transactionManager1(EntityManagerFactory dataSource1EntityManagerFactory) {
            return new JpaTransactionManager(dataSource1EntityManagerFactory);
        }
    
        private Map<String, Object> getVendorProperties() {
            return new LinkedHashMap<>(
                this.hibernateProperties
                    .determineHibernateProperties(jpaProperties.getProperties(),
                        new HibernateSettings()
                            // Spring Boot's HibernateDefaultDdlAutoProvider is not available here
                            .hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers)
                    )
            );
        }
    }
    

    关于hibernate - 使用 Spring Boot 2.1+ 为 Hibernate 配置缓存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59679175/

    相关文章:

    hibernate - 无法读取架构文档'http ://java. sun.com/xml/ns/persistence/persistence_2_0.xsd

    java - 何时使用 Java Cache 以及它与 HashMap 有何不同?

    java - Hibernate Envers - 获取已更改的字段

    java - 解决 JPA/Hibernate EntityNotFoundException

    postgresql - 在没有调用属性的情况下获取 n+1 个 Hibernate 惰性关系 - Kotlin

    java - Google APP Engine 和 cloud sql::无法在 Google cloud sql 中连接 Spring boot 应用程序(我的 sql)

    java - 动态数据源作为 Spring Boot + Hibernate 中的第二个数据源

    gradle - Spring Boot Vaadin静态内容

    java - 如何使用 JCache 注释 @CacheRemoveAll 清除多个缓存?

    java - Infinispan-10.0.1.Final : No marshaller registered for Java type java. util.UUID