使用Spring Boot 2.1+配置Hibernate的缓存

9

背景和问题

我试图在Spring Boot 2.2中使用EHCache配置Hibernate,但似乎我做错了些什么。 我查看了几个教程和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
2个回答

8

虽然不容易,但我找到了问题的原因和解决方法。

原因

基本上,这个问题源于我自己配置了LocalContainerEntityManagerFactoryBean

如果你没有配置,Spring Boot将使用它的自动配置来创建所有需要的内容,包括供应商属性(即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(覆盖这些机制是有代码坏味道的,所以这不是正确的方法)和/或内部/受保护的类(您无法直接调用这些类)。

可能的脆弱性?

这意味着您基本上需要将Spring Boot的代码复制到自己的代码中,这可能会导致在未来升级Spring Boot时出现一些脆弱性问题(或者仅仅是因为您的代码无法从最新的错误/性能修复中受益)。 从这个角度来看,我对我在这里提供的解决方案并不是很满意。

详细指南

您所依赖的Bean

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

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

1
非常感谢@Chop,你帮我节省了很多时间。 - Gaël Marziou
当答案有用时,总是很愉快的。 :) - Chop

0
你可以直接注入并将 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提供的属性和应用程序.properties中的属性将不会被注入。 - Hamza Assada

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接