Add support for advanced customization of Hibernate settings

As HibernateJpaConfiguration is package private, it is no longer
possible to extend the default Spring Boot configuration to apply
advanced settings. The most notable use case for this is the
customization of Hibernate properties using instance value vs. string
value that can be set using the "spring.jpa.properties" namespace.

This commit adds a HibernatePropertiesCustomizer callback interface that
can be implemented to tune Hibernate properties at will.

Closes gh-11211
pull/11492/merge
Stephane Nicoll 7 years ago
parent 268b97bf98
commit 59d5ed5842

@ -87,13 +87,16 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration {
private final ImplicitNamingStrategy implicitNamingStrategy;
private final List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers;
HibernateJpaConfiguration(DataSource dataSource, JpaProperties jpaProperties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers,
ObjectProvider<Collection<DataSourcePoolMetadataProvider>> metadataProviders,
ObjectProvider<List<SchemaManagementProvider>> providers,
ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy,
ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy) {
ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy,
ObjectProvider<List<HibernatePropertiesCustomizer>> hibernatePropertiesCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager,
transactionManagerCustomizers);
this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider(
@ -102,6 +105,8 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration {
metadataProviders.getIfAvailable());
this.physicalNamingStrategy = physicalNamingStrategy.getIfAvailable();
this.implicitNamingStrategy = implicitNamingStrategy.getIfAvailable();
this.hibernatePropertiesCustomizers = hibernatePropertiesCustomizers
.getIfAvailable(() -> Collections.EMPTY_LIST);
}
@Override
@ -111,14 +116,13 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration {
@Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> vendorProperties = new LinkedHashMap<>();
String defaultDdlMode = this.defaultDdlAutoProvider
.getDefaultDdlAuto(getDataSource());
vendorProperties.putAll(getProperties()
return new LinkedHashMap<>(getProperties()
.getHibernateProperties(new HibernateSettings().ddlAuto(defaultDdlMode)
.implicitNamingStrategy(this.implicitNamingStrategy)
.physicalNamingStrategy(this.physicalNamingStrategy)));
return vendorProperties;
.physicalNamingStrategy(this.physicalNamingStrategy)
.hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers)));
}
@Override

@ -0,0 +1,36 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.Map;
/**
* Callback interface that can be implemented by beans wishing to customize the Hibernate
* properties before it is used by an auto-configured {@code EntityManagerFactory}.
*
* @author Stephane Nicoll
*/
@FunctionalInterface
public interface HibernatePropertiesCustomizer {
/**
* Customize the specified JPA vendor properties.
* @param hibernateProperties the current JPA vendor properties
*/
void customize(Map<String, Object> hibernateProperties);
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,9 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.ArrayList;
import java.util.Collection;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
@ -33,6 +36,8 @@ public class HibernateSettings {
private PhysicalNamingStrategy physicalNamingStrategy;
private Collection<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers;
public HibernateSettings ddlAuto(String ddlAuto) {
this.ddlAuto = ddlAuto;
return this;
@ -62,4 +67,15 @@ public class HibernateSettings {
return this.physicalNamingStrategy;
}
public HibernateSettings hibernatePropertiesCustomizers(
Collection<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
this.hibernatePropertiesCustomizers = new ArrayList<>();
this.hibernatePropertiesCustomizers.addAll(hibernatePropertiesCustomizers);
return this;
}
public Collection<HibernatePropertiesCustomizer> getHibernatePropertiesCustomizers() {
return this.hibernatePropertiesCustomizers;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -28,6 +29,7 @@ import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@ -222,6 +224,12 @@ public class JpaProperties {
else {
result.remove("hibernate.hbm2ddl.auto");
}
Collection<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers =
settings.getHibernatePropertiesCustomizers();
if (!ObjectUtils.isEmpty(hibernatePropertiesCustomizers)) {
hibernatePropertiesCustomizers.forEach((customizer)
-> customizer.customize(result));
}
return result;
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.orm.jpa.test.City;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.test.util.ReflectionTestUtils;
@ -55,6 +56,7 @@ import static org.mockito.Mockito.mock;
public class CustomHibernateJpaAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.generate-unique-name=true")
.withUserConfiguration(TestConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class));
@ -86,10 +88,23 @@ public class CustomHibernateJpaAutoConfigurationTests {
.getVendorProperties();
assertThat(hibernateProperties
.get("hibernate.implicit_naming_strategy")).isEqualTo(
NamingStrategyConfiguration.implicitNamingStrategy);
NamingStrategyConfiguration.implicitNamingStrategy);
assertThat(hibernateProperties
.get("hibernate.physical_naming_strategy")).isEqualTo(
NamingStrategyConfiguration.physicalNamingStrategy);
NamingStrategyConfiguration.physicalNamingStrategy);
});
}
@Test
public void hibernatePropertiesCustomizersAreAppliedInOrder() {
this.contextRunner
.withUserConfiguration(HibernatePropertiesCustomizerConfiguration.class)
.run((context) -> {
HibernateJpaConfiguration jpaConfiguration = context
.getBean(HibernateJpaConfiguration.class);
Map<String, Object> hibernateProperties = jpaConfiguration
.getVendorProperties();
assertThat(hibernateProperties.get("test.counter")).isEqualTo(2);
});
}
@ -149,4 +164,21 @@ public class CustomHibernateJpaAutoConfigurationTests {
}
@Configuration
static class HibernatePropertiesCustomizerConfiguration {
@Bean
@Order(2)
public HibernatePropertiesCustomizer sampleCustomizer() {
return ((hibernateProperties) -> hibernateProperties.put("test.counter", 2));
}
@Bean
@Order(1)
public HibernatePropertiesCustomizer anotherCustomizer() {
return ((hibernateProperties) -> hibernateProperties.put("test.counter", 1));
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.orm.jpa;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
@ -126,6 +127,40 @@ public class JpaPropertiesTests {
}));
}
@Test
public void hibernatePropertiesCustomizerTakePrecedenceOverStrategyInstancesAndNamingStrategyProperties() {
this.contextRunner
.withPropertyValues(
"spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit",
"spring.jpa.hibernate.naming.physical-strategy:com.example.Physical"
).run(assertJpaProperties((properties) -> {
ImplicitNamingStrategy implicitStrategy = mock(ImplicitNamingStrategy.class);
PhysicalNamingStrategy physicalStrategy = mock(PhysicalNamingStrategy.class);
ImplicitNamingStrategy effectiveImplicitStrategy = mock(
ImplicitNamingStrategy.class);
PhysicalNamingStrategy effectivePhysicalStrategy = mock(
PhysicalNamingStrategy.class);
HibernatePropertiesCustomizer customizer = (hibernateProperties) -> {
hibernateProperties.put("hibernate.implicit_naming_strategy",
effectiveImplicitStrategy);
hibernateProperties.put("hibernate.physical_naming_strategy",
effectivePhysicalStrategy);
};
Map<String, Object> hibernateProperties = properties
.getHibernateProperties(new HibernateSettings().ddlAuto("none")
.implicitNamingStrategy(implicitStrategy)
.physicalNamingStrategy(physicalStrategy)
.hibernatePropertiesCustomizers(
Collections.singleton(customizer)));
assertThat(hibernateProperties).contains(
entry("hibernate.implicit_naming_strategy", effectiveImplicitStrategy),
entry("hibernate.physical_naming_strategy", effectivePhysicalStrategy));
assertThat(hibernateProperties)
.doesNotContainKeys("hibernate.ejb.naming_strategy");
}));
}
@Test
public void hibernate5CustomNamingStrategiesViaJpaProperties() {
this.contextRunner

@ -1787,6 +1787,11 @@ In addition, all properties in `+spring.jpa.properties.*+` are passed through as
JPA properties (with the prefix stripped) when the local `EntityManagerFactory` is
created.
TIP: if you need to apply advanced customization to Hibernate properties, consider
registering a `HibernatePropertiesCustomizer` bean that will be invoked prior to creating
the `EntityManagerFactory`. This takes precedence to anything that is applied by the
auto-configuration.
[[howto-configure-hibernate-naming-strategy]]

Loading…
Cancel
Save