【问题标题】:Configuring caching for Hibernate with Spring Boot 2.1+使用 Spring Boot 2.1+ 为 Hibernate 配置缓存
【发布时间】:2020-01-10 09:48:44
【问题描述】:

背景和问题

我正在尝试在 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);
    }
}

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

【问题讨论】:

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


    【解决方案1】:

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

    原因

    基本上,问题在于我自己配置​​了LocalContainerEntityManagerFactoryBean

    如果你不这样做,Spring Boot 将使用它的 AutoConfigurations 来创建一切都很好,包括供应商属性(spring.jpa.properties 下的所有内容)、休眠属性(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(覆盖这些是代码异味,所以这不是这样做的方法)和/或内部/受保护的类(您不能直接调用)。

    可能的脆性?

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

    详细指南

    你依赖的 Bean

    您需要将以下 bean 注入到您的数据源配置中:

    • org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties
    • org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
    • List&lt;org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer&gt;

    要执行的操作

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

    【讨论】:

    • 非常感谢@Chop,你为我节省了很多时间
    • 当答案有用时总是很高兴。 :)
    【解决方案2】:

    您可以简单地将cacheManager 注入并放入您的dataSource1EntityManagerFactory。不用HibernatePropertiesCustomizer

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            DataSource dataSource1,
            javax.cache.CacheManager cacheManager) {
        return builder
                .dataSource(dataSource1)
                .properties(Map.of(org.hibernate.cache.jcache.ConfigSettings.CACHE_MANAGER, cacheManager))
                .packages("com.example.demo.one.dto")
                .build();
    }
    

    【讨论】:

    • 但是 jpa-hibernate 剩下的默认属性和 spring boot 提供的属性和来自应用程序的属性。如果我们这样做,属性将不会被注入
    猜你喜欢
    • 2020-03-16
    • 2014-06-21
    • 1970-01-01
    • 2018-06-02
    • 1970-01-01
    • 2015-10-13
    • 2019-11-21
    • 1970-01-01
    • 2021-01-23
    相关资源
    最近更新 更多