Настройка кэширования для Hibernate с помощью Spring Boot 2.1+ - PullRequest
2 голосов
/ 10 января 2020

Контекст и вопрос

Я пытаюсь настроить EHCache с Hibernate в Spring Boot 2.2, но, похоже, я делаю что-то не так. Я просмотрел несколько учебных пособий и вопросов SO, но не нашел того, что полностью соответствовало бы моему подходу.

Я выбрал подход к конфигурации jcache без XML для кэширования. Однако 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, какой менеджер кэша он должен использовать. Компонент создается, но никогда не вызывается, поэтому он теряет всю свою привлекательность и предназначение.

Кто-нибудь знает, что я делаю неправильно и как я должен связать Hibernate, чтобы использовать диспетчер кэша, который я уже настроил вместо того, чтобы создавать свою собственную?

Я сравнил свою конфигурацию с той, которую генерирует JHipster . Это выглядит очень похоже, хотя их HibernatePropertiesCustomizer называется . Мне не удалось определить разницу между их конфигурацией кэша и моей.

Заметки из более поздних тестов (правки)

Это, похоже, связано с моей конфигурацией источника данных (см. Код ниже). Я попытался удалить его и включить мою конфигурацию JPA более простым способом, и HibernatePropertiesCustomizer действительно вызывается, как и ожидалось.

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

На самом деле, настроив мои источники данных вручную (потому что мне нужно обработать два разных источника данных ), Я обхожу Spring Boot's 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: ломбок как комфорт

Конфигурация кэша

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

1 Ответ

1 голос
/ 13 января 2020

Это было непросто, но я нашел причину и решение.

Причина

В основном, проблема заключается в том, что я сам настраиваю LocalContainerEntityManagerFactoryBean.

Если вы этого не сделаете, Spring Boot будет использовать свои автоконфигурации для создания всего хорошего и хорошего, включая свойства вендора (все, что у вас под 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. Только «почти», потому что большинство из этих механизмов полагаются на автоконфигурации (их переопределение - это запах кода, так что это не так) и / или внутренние / защищенные классы (которые вы не можете вызвать напрямую).

Возможная хрупкость?

Это означает, что вам, по сути, нужно скопировать код Spring Boot в свой собственный, возможно, создать некоторую хрупкость в отношении будущих обновлений Spring Boot (или просто, что ваш код не принесет пользы из последних исправлений ошибок / выступлений). Исходя из этого, я не большой поклонник решения, которое я здесь представляю.

Подробное руководство

Бины, от которых вы зависите

Вам нужно будет добавить следующие бины в вашей конфигурации источника данных:

  • 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, я загрузил свойства продавца. Здесь я снова пропустил некоторые автоматические настройки c, на которые вы можете посмотреть (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)
                )
        );
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...