diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java index 845fe59bdb..7b7ceea125 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java @@ -92,8 +92,8 @@ class DataSourceInitializedPublisher implements BeanPostProcessor { } String defaultDdlAuto = (EmbeddedDatabaseConnection.isEmbedded(dataSource) ? "create-drop" : "none"); - Map hibernate = this.properties - .getHibernateProperties(defaultDdlAuto); + Map hibernate = this.properties + .getHibernateProperties(new HibernateSettings().ddlAuto(defaultDdlAuto)); if (hibernate.containsKey("hibernate.hbm2ddl.auto")) { return true; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java index 517070a5af..ed4466a228 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.java @@ -26,6 +26,8 @@ import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.hibernate.boot.model.naming.ImplicitNamingStrategy; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; @@ -81,17 +83,25 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { private DataSourcePoolMetadataProvider poolMetadataProvider; + private final PhysicalNamingStrategy physicalNamingStrategy; + + private final ImplicitNamingStrategy implicitNamingStrategy; + HibernateJpaConfiguration(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider jtaTransactionManager, ObjectProvider transactionManagerCustomizers, ObjectProvider> metadataProviders, - ObjectProvider> providers) { + ObjectProvider> providers, + ObjectProvider physicalNamingStrategy, + ObjectProvider implicitNamingStrategy) { super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers); this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider( providers.getIfAvailable(Collections::emptyList)); this.poolMetadataProvider = new CompositeDataSourcePoolMetadataProvider( metadataProviders.getIfAvailable()); + this.physicalNamingStrategy = physicalNamingStrategy.getIfAvailable(); + this.implicitNamingStrategy = implicitNamingStrategy.getIfAvailable(); } @Override @@ -104,7 +114,10 @@ class HibernateJpaConfiguration extends JpaBaseConfiguration { Map vendorProperties = new LinkedHashMap<>(); String defaultDdlMode = this.defaultDdlAutoProvider .getDefaultDdlAuto(getDataSource()); - vendorProperties.putAll(getProperties().getHibernateProperties(defaultDdlMode)); + vendorProperties.putAll(getProperties() + .getHibernateProperties(new HibernateSettings().ddlAuto(defaultDdlMode) + .implicitNamingStrategy(this.implicitNamingStrategy) + .physicalNamingStrategy(this.physicalNamingStrategy))); return vendorProperties; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateSettings.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateSettings.java new file mode 100644 index 0000000000..7711c0312a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateSettings.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2017 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 org.hibernate.boot.model.naming.ImplicitNamingStrategy; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; + +/** + * Settings to apply when configuring Hibernate. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class HibernateSettings { + + private String ddlAuto; + + private ImplicitNamingStrategy implicitNamingStrategy; + + private PhysicalNamingStrategy physicalNamingStrategy; + + public HibernateSettings ddlAuto(String ddlAuto) { + this.ddlAuto = ddlAuto; + return this; + } + + public String getDdlAuto() { + return this.ddlAuto; + } + + public HibernateSettings implicitNamingStrategy( + ImplicitNamingStrategy implicitNamingStrategy) { + this.implicitNamingStrategy = implicitNamingStrategy; + return this; + } + + public ImplicitNamingStrategy getImplicitNamingStrategy() { + return this.implicitNamingStrategy; + } + + public HibernateSettings physicalNamingStrategy( + PhysicalNamingStrategy physicalNamingStrategy) { + this.physicalNamingStrategy = physicalNamingStrategy; + return this; + } + + public PhysicalNamingStrategy getPhysicalNamingStrategy() { + return this.physicalNamingStrategy; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java index cfba51ff58..d4bb20ba96 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -23,6 +23,9 @@ import java.util.Map; import javax.sql.DataSource; +import org.hibernate.boot.model.naming.ImplicitNamingStrategy; +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.StringUtils; @@ -143,11 +146,11 @@ public class JpaProperties { /** * Get configuration properties for the initialization of the main Hibernate * EntityManagerFactory. - * @param defaultDdlAuto the default DDL auto (can be {@code null}) + * @param settings the settings to apply when determining the configuration properties * @return some Hibernate properties for configuration */ - public Map getHibernateProperties(String defaultDdlAuto) { - return this.hibernate.getAdditionalProperties(this.properties, defaultDdlAuto); + public Map getHibernateProperties(HibernateSettings settings) { + return this.hibernate.getAdditionalProperties(this.properties, settings); } /** @@ -205,12 +208,14 @@ public class JpaProperties { return this.naming; } - private Map getAdditionalProperties(Map existing, - String defaultDdlAuto) { - Map result = new HashMap<>(existing); + private Map getAdditionalProperties(Map existing, + HibernateSettings settings) { + Map result = new HashMap<>(existing); applyNewIdGeneratorMappings(result); - getNaming().applyNamingStrategies(result); - String ddlAuto = determineDdlAuto(existing, defaultDdlAuto); + getNaming().applyNamingStrategies(result, + settings.getImplicitNamingStrategy(), + settings.getPhysicalNamingStrategy()); + String ddlAuto = determineDdlAuto(existing, settings.getDdlAuto()); if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) { result.put("hibernate.hbm2ddl.auto", ddlAuto); } @@ -220,7 +225,7 @@ public class JpaProperties { return result; } - private void applyNewIdGeneratorMappings(Map result) { + private void applyNewIdGeneratorMappings(Map result) { if (this.useNewIdGeneratorMappings != null) { result.put(USE_NEW_ID_GENERATOR_MAPPINGS, this.useNewIdGeneratorMappings.toString()); @@ -277,15 +282,21 @@ public class JpaProperties { this.physicalStrategy = physicalStrategy; } - private void applyNamingStrategies(Map properties) { - applyNamingStrategy(properties, "hibernate.implicit_naming_strategy", - this.implicitStrategy, DEFAULT_IMPLICIT_STRATEGY); - applyNamingStrategy(properties, "hibernate.physical_naming_strategy", - this.physicalStrategy, DEFAULT_PHYSICAL_STRATEGY); + private void applyNamingStrategies(Map properties, + ImplicitNamingStrategy implicitStrategyBean, + PhysicalNamingStrategy physicalStrategyBean) { + applyNamingStrategy(properties, + "hibernate.implicit_naming_strategy", implicitStrategyBean != null + ? implicitStrategyBean : this.implicitStrategy, + DEFAULT_IMPLICIT_STRATEGY); + applyNamingStrategy(properties, + "hibernate.physical_naming_strategy", physicalStrategyBean != null + ? physicalStrategyBean : this.physicalStrategy, + DEFAULT_PHYSICAL_STRATEGY); } - private void applyNamingStrategy(Map properties, String key, - String strategy, String defaultStrategy) { + private void applyNamingStrategy(Map properties, String key, + Object strategy, Object defaultStrategy) { if (strategy != null) { properties.put(key, strategy); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java index 2e6797cb17..a651bbeffd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/CustomHibernateJpaAutoConfigurationTests.java @@ -23,6 +23,10 @@ import java.util.Map; import javax.sql.DataSource; +import org.hibernate.boot.model.naming.ImplicitNamingStrategy; +import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -63,13 +67,32 @@ public class CustomHibernateJpaAutoConfigurationTests { + "org.hibernate.cfg.naming.ImprovedNamingStrategyDelegator") .run((context) -> { JpaProperties bean = context.getBean(JpaProperties.class); - Map hibernateProperties = bean - .getHibernateProperties("create-drop"); + Map hibernateProperties = bean.getHibernateProperties( + new HibernateSettings().ddlAuto("create-drop")); assertThat(hibernateProperties.get("hibernate.ejb.naming_strategy")) .isNull(); }); } + @Test + public void namingStrategyBeansAreUsed() { + this.contextRunner.withUserConfiguration(NamingStrategyConfiguration.class) + .withPropertyValues( + "spring.datasource.url:jdbc:h2:mem:naming-strategy-beans") + .run((context) -> { + HibernateJpaConfiguration jpaConfiguration = context + .getBean(HibernateJpaConfiguration.class); + Map hibernateProperties = jpaConfiguration + .getVendorProperties(); + assertThat(hibernateProperties + .get("hibernate.implicit_naming_strategy")).isEqualTo( + NamingStrategyConfiguration.implicitNamingStrategy); + assertThat(hibernateProperties + .get("hibernate.physical_naming_strategy")).isEqualTo( + NamingStrategyConfiguration.physicalNamingStrategy); + }); + } + @Test public void defaultDatabaseForH2() { this.contextRunner.withPropertyValues("spring.datasource.url:jdbc:h2:mem:testdb", @@ -107,4 +130,23 @@ public class CustomHibernateJpaAutoConfigurationTests { } + @Configuration + static class NamingStrategyConfiguration { + + static final ImplicitNamingStrategy implicitNamingStrategy = new ImplicitNamingStrategyJpaCompliantImpl(); + + static final PhysicalNamingStrategy physicalNamingStrategy = new PhysicalNamingStrategyStandardImpl(); + + @Bean + public ImplicitNamingStrategy implicitNamingStrategy() { + return implicitNamingStrategy; + } + + @Bean + public PhysicalNamingStrategy physicalNamingStrategy() { + return physicalNamingStrategy; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java index 5406732976..ca80d9d5f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java @@ -23,6 +23,8 @@ import java.util.Map; import javax.sql.DataSource; +import org.hibernate.boot.model.naming.ImplicitNamingStrategy; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.hibernate.cfg.AvailableSettings; import org.junit.After; import org.junit.Test; @@ -61,8 +63,8 @@ public class JpaPropertiesTests { @Test public void noCustomNamingStrategy() throws Exception { JpaProperties properties = load(); - Map hibernateProperties = properties - .getHibernateProperties("none"); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none")); assertThat(hibernateProperties) .doesNotContainKeys("hibernate.ejb.naming_strategy"); assertThat(hibernateProperties).containsEntry( @@ -78,8 +80,8 @@ public class JpaPropertiesTests { JpaProperties properties = load( "spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit", "spring.jpa.hibernate.naming.physical-strategy:com.example.Physical"); - Map hibernateProperties = properties - .getHibernateProperties("none"); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none")); assertThat(hibernateProperties).contains( entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), entry("hibernate.physical_naming_strategy", "com.example.Physical")); @@ -87,13 +89,48 @@ public class JpaPropertiesTests { .doesNotContainKeys("hibernate.ejb.naming_strategy"); } + @Test + public void namingStrategyInstancesCanBeUsed() throws Exception { + JpaProperties properties = load(); + ImplicitNamingStrategy implicitStrategy = mock(ImplicitNamingStrategy.class); + PhysicalNamingStrategy physicalStrategy = mock(PhysicalNamingStrategy.class); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none") + .implicitNamingStrategy(implicitStrategy) + .physicalNamingStrategy(physicalStrategy)); + assertThat(hibernateProperties).contains( + entry("hibernate.implicit_naming_strategy", implicitStrategy), + entry("hibernate.physical_naming_strategy", physicalStrategy)); + assertThat(hibernateProperties) + .doesNotContainKeys("hibernate.ejb.naming_strategy"); + } + + @Test + public void namingStrategyInstancesTakePrecedenceOverNamingStrategyProperties() + throws Exception { + JpaProperties properties = load( + "spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit", + "spring.jpa.hibernate.naming.physical-strategy:com.example.Physical"); + ImplicitNamingStrategy implicitStrategy = mock(ImplicitNamingStrategy.class); + PhysicalNamingStrategy physicalStrategy = mock(PhysicalNamingStrategy.class); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none") + .implicitNamingStrategy(implicitStrategy) + .physicalNamingStrategy(physicalStrategy)); + assertThat(hibernateProperties).contains( + entry("hibernate.implicit_naming_strategy", implicitStrategy), + entry("hibernate.physical_naming_strategy", physicalStrategy)); + assertThat(hibernateProperties) + .doesNotContainKeys("hibernate.ejb.naming_strategy"); + } + @Test public void hibernate5CustomNamingStrategiesViaJpaProperties() throws Exception { JpaProperties properties = load( "spring.jpa.properties.hibernate.implicit_naming_strategy:com.example.Implicit", "spring.jpa.properties.hibernate.physical_naming_strategy:com.example.Physical"); - Map hibernateProperties = properties - .getHibernateProperties("none"); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none")); // You can override them as we don't provide any default assertThat(hibernateProperties).contains( entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), @@ -105,8 +142,8 @@ public class JpaPropertiesTests { @Test public void useNewIdGeneratorMappingsDefault() throws Exception { JpaProperties properties = load(); - Map hibernateProperties = properties - .getHibernateProperties("none"); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none")); assertThat(hibernateProperties) .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true"); } @@ -115,8 +152,8 @@ public class JpaPropertiesTests { public void useNewIdGeneratorMappingsFalse() throws Exception { JpaProperties properties = load( "spring.jpa.hibernate.use-new-id-generator-mappings:false"); - Map hibernateProperties = properties - .getHibernateProperties("none"); + Map hibernateProperties = properties + .getHibernateProperties(new HibernateSettings().ddlAuto("none")); assertThat(hibernateProperties) .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false"); } diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc index dad0ad080c..7e451f0ba7 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1774,13 +1774,15 @@ Hibernate uses {hibernate-documentation}#naming[two different naming strategies] names from the object model to the corresponding database names. The fully qualified class name of the physical and the implicit strategy implementations can be configured by setting the `spring.jpa.hibernate.naming.physical-strategy` and -`spring.jpa.hibernate.naming.implicit-strategy` properties, respectively. +`spring.jpa.hibernate.naming.implicit-strategy` properties, respectively. Alternatively, +if `ImplicitNamingStrategy` or `PhysicalNamingStrategy` beans are available in the +application context, Hibernate will be automatically configured to use them. -By default, Spring Boot configures the physical naming strategy with `SpringPhysicalNamingStrategy`. -This implementation provides the same table structure as Hibernate 4: all dots -are replaced by underscores and camel casing is replaced by underscores as well. By -default, all table names are generated in lower case, but it is possible to override that -flag if your schema requires it. +By default, Spring Boot configures the physical naming strategy with +`SpringPhysicalNamingStrategy`. This implementation provides the same table structure as +Hibernate 4: all dots are replaced by underscores and camel casing is replaced by +underscores as well. By default, all table names are generated in lower case, but it is +possible to override that flag if your schema requires it. For example, a `TelephoneNumber` entity is mapped to the `telephone_number` table. @@ -1791,6 +1793,15 @@ If you prefer to use Hibernate 5's default instead, set the following property: spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl ---- +Alternatively, configure the following bean: + +[source,java,indent=0,subs="verbatim,quotes,attributes"] +---- + @Bean + public PhysicalNamingStrategy physicalNamingStrategy() { + return new PhysicalNamingStrategyStandardImpl(); + } +---- See {sc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{sc-ext}[`HibernateJpaAutoConfiguration`] and {sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[`JpaBaseConfiguration`]