diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateVersion.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateVersion.java new file mode 100644 index 0000000000..25b6d6534a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateVersion.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2016 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.springframework.util.ClassUtils; + +/** + * Supported Hibernate versions. + * + * @author Phillip Webb + */ +enum HibernateVersion { + + /** + * Version 4. + */ + V4, + + /** + * Version 5. + */ + V5; + + private static final String HIBERNATE_5_CLASS = "org.hibernate.boot.model." + + "naming.PhysicalNamingStrategy"; + + private static HibernateVersion running; + + public static HibernateVersion getRunning() { + if (running == null) { + setRunning(ClassUtils.isPresent(HIBERNATE_5_CLASS, null) ? V5 : V4); + } + return running; + } + + static void setRunning(HibernateVersion running) { + HibernateVersion.running = running; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java index 8babe4a5b8..35c2a79c2a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -23,6 +23,7 @@ import javax.sql.DataSource; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.orm.jpa.vendor.Database; import org.springframework.util.StringUtils; @@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; * * @author Dave Syer * @author Andy Wilkinson + * @author Stephane Nicoll * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.jpa") @@ -125,12 +127,8 @@ public class JpaProperties { public static class Hibernate { - private static final String DEFAULT_NAMING_STRATEGY = "org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy"; - - /** - * Naming strategy fully qualified name. - */ - private Class namingStrategy; + private static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id." + + "new_generator_mappings"; /** * DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" @@ -139,12 +137,25 @@ public class JpaProperties { */ private String ddlAuto; - public Class getNamingStrategy() { - return this.namingStrategy; + /** + * Use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE. This is + * actually a shortcut for the "hibernate.id.new_generator_mappings" property. + * When not specified will default to "false" with Hibernate 5 for back + * compatibility. + */ + private Boolean useNewIdGeneratorMappings; + + private final Naming naming = new Naming(); + + @Deprecated + @DeprecatedConfigurationProperty(replacement = "spring.jpa.hibernate.naming.strategy") + public String getNamingStrategy() { + return getNaming().getStrategy(); } - public void setNamingStrategy(Class namingStrategy) { - this.namingStrategy = namingStrategy; + @Deprecated + public void setNamingStrategy(String namingStrategy) { + getNaming().setStrategy(namingStrategy); } public String getDdlAuto() { @@ -155,13 +166,23 @@ public class JpaProperties { this.ddlAuto = ddlAuto; } + public boolean isUseNewIdGeneratorMappings() { + return this.useNewIdGeneratorMappings; + } + + public void setUseNewIdGeneratorMappings(boolean useNewIdGeneratorMappings) { + this.useNewIdGeneratorMappings = useNewIdGeneratorMappings; + } + + public Naming getNaming() { + return this.naming; + } + private Map getAdditionalProperties(Map existing, DataSource dataSource) { Map result = new HashMap(existing); - if (!existing.containsKey("hibernate." + "ejb.naming_strategy_delegator")) { - result.put("hibernate.ejb.naming_strategy", - getHibernateNamingStrategy(existing)); - } + applyNewIdGeneratorMappings(result); + getNaming().applyNamingStrategy(result); String ddlAuto = getOrDeduceDdlAuto(existing, dataSource); if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) { result.put("hibernate.hbm2ddl.auto", ddlAuto); @@ -172,12 +193,15 @@ public class JpaProperties { return result; } - private String getHibernateNamingStrategy(Map existing) { - if (!existing.containsKey("hibernate." + "ejb.naming_strategy") - && this.namingStrategy != null) { - return this.namingStrategy.getName(); + private void applyNewIdGeneratorMappings(Map result) { + if (this.useNewIdGeneratorMappings != null) { + result.put(USE_NEW_ID_GENERATOR_MAPPINGS, + this.useNewIdGeneratorMappings.toString()); + } + else if (HibernateVersion.getRunning() == HibernateVersion.V5 + && !result.containsKey(USE_NEW_ID_GENERATOR_MAPPINGS)) { + result.put(USE_NEW_ID_GENERATOR_MAPPINGS, "false"); } - return DEFAULT_NAMING_STRATEGY; } private String getOrDeduceDdlAuto(Map existing, @@ -203,4 +227,96 @@ public class JpaProperties { } + public static class Naming { + + private static final String DEFAULT_HIBERNATE4_STRATEGY = "org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy"; + + private static final String DEFAULT_PHYSICAL_STRATEGY = "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"; + + /** + * Hibernate 5 implicit naming strategy fully qualified name. + */ + private String implicitStrategy; + + /** + * Hibernate 5 physical naming strategy fully qualified name. + */ + private String physicalStrategy; + + /** + * Hibernate 4 naming strategy fully qualified name. Not supported with Hibernate + * 5. + */ + private String strategy; + + public String getImplicitStrategy() { + return this.implicitStrategy; + } + + public void setImplicitStrategy(String implicitStrategy) { + this.implicitStrategy = implicitStrategy; + } + + public String getPhysicalStrategy() { + return this.physicalStrategy; + } + + public void setPhysicalStrategy(String physicalStrategy) { + this.physicalStrategy = physicalStrategy; + } + + public String getStrategy() { + return this.strategy; + } + + public void setStrategy(String strategy) { + this.strategy = strategy; + } + + private void applyNamingStrategy(Map properties) { + switch (HibernateVersion.getRunning()) { + case V4: + applyHibernate4NamingStrategy(properties); + break; + case V5: + applyHibernate5NamingStrategy(properties); + break; + } + } + + private void applyHibernate5NamingStrategy(Map properties) { + applyHibernate5NamingStrategy(properties, + "hibernate.implicit_naming_strategy", this.implicitStrategy, null); + applyHibernate5NamingStrategy(properties, + "hibernate.physical_naming_strategy", this.physicalStrategy, + DEFAULT_PHYSICAL_STRATEGY); + } + + private void applyHibernate5NamingStrategy(Map properties, + String key, String strategy, String defaultStrategy) { + if (strategy != null) { + properties.put(key, strategy); + } + else if (defaultStrategy != null && !properties.containsKey(key)) { + properties.put(key, defaultStrategy); + } + } + + private void applyHibernate4NamingStrategy(Map properties) { + if (!properties.containsKey("hibernate.ejb.naming_strategy_delegator")) { + properties.put("hibernate.ejb.naming_strategy", + getHibernate4NamingStrategy(properties)); + } + } + + private String getHibernate4NamingStrategy(Map existing) { + if (!existing.containsKey("hibernate.ejb.naming_strategy") + && this.strategy != null) { + return this.strategy; + } + return DEFAULT_HIBERNATE4_STRATEGY; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index e16833216b..8b52e14b74 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -447,7 +447,29 @@ ] }, { - "name": "spring.jpa.hibernate.naming-strategy", + "name": "spring.jpa.hibernate.naming.implicit-strategy", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.hibernate.boot.model.naming.ImplicitNamingStrategy" + } + } + ] + }, + { + "name": "spring.jpa.hibernate.naming.physical-strategy", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.hibernate.boot.model.naming.PhysicalNamingStrategy" + } + } + ] + }, + { + "name": "spring.jpa.hibernate.naming.strategy", "providers": [ { "name": "class-reference", diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java index 03a2523504..cc26cf7294 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/city/City.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 5ca4e4a913..7ff22c36ec 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -26,6 +26,7 @@ import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; @@ -49,6 +50,11 @@ import static org.assertj.core.api.Assertions.assertThat; public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTests { + @After + public void cleanup() { + HibernateVersion.setRunning(null); + } + @Override protected Class getAutoConfigureClass() { return HibernateJpaAutoConfiguration.class; @@ -80,6 +86,7 @@ public class HibernateJpaAutoConfigurationTests @Test public void testCustomNamingStrategy() throws Exception { + HibernateVersion.setRunning(HibernateVersion.V4); EnvironmentTestUtils.addEnvironment(this.context, "spring.jpa.hibernate.namingStrategy:" + "org.hibernate.cfg.EJB3NamingStrategy"); @@ -94,6 +101,7 @@ public class HibernateJpaAutoConfigurationTests @Test public void testCustomNamingStrategyViaJpaProperties() throws Exception { + HibernateVersion.setRunning(HibernateVersion.V4); EnvironmentTestUtils.addEnvironment(this.context, "spring.jpa.properties.hibernate.ejb.naming_strategy:" + "org.hibernate.cfg.EJB3NamingStrategy"); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java new file mode 100644 index 0000000000..cbe98e2830 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/JpaPropertiesTests.java @@ -0,0 +1,176 @@ +/* + * Copyright 2012-2016 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.sql.SQLException; +import java.util.Map; + +import javax.sql.DataSource; + +import org.hibernate.cfg.AvailableSettings; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link JpaProperties}. + * + * @author Stephane Nicoll + */ +public class JpaPropertiesTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + HibernateVersion.setRunning(null); + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void hibernate4CustomNamingStrategy() throws Exception { + JpaProperties properties = load(HibernateVersion.V4, + "spring.jpa.hibernate.naming.strategy:" + + "org.hibernate.cfg.EJB3NamingStrategy"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties).contains(entry("hibernate.ejb.naming_strategy", + "org.hibernate.cfg.EJB3NamingStrategy")); + assertThat(hibernateProperties).doesNotContainKeys( + "hibernate.implicit_naming_strategy", + "hibernate.physical_naming_strategy"); + } + + @Test + public void hibernate4CustomNamingStrategyViaJpaProperties() throws Exception { + JpaProperties properties = load(HibernateVersion.V4, + "spring.jpa.properties.hibernate.ejb.naming_strategy:" + + "org.hibernate.cfg.EJB3NamingStrategy"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + String actual = hibernateProperties.get("hibernate.ejb.naming_strategy"); + // You can't override this one from spring.jpa.properties because it has an + // opinionated default + assertThat(actual).isNotEqualTo("org.hibernate.cfg.EJB3NamingStrategy"); + } + + @Test + public void hibernate5NoCustomNamingStrategy() throws Exception { + JpaProperties properties = load(HibernateVersion.V5); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties).doesNotContainKeys( + "hibernate.ejb.naming_strategy", "hibernate.implicit_naming_strategy"); + assertThat(hibernateProperties).containsEntry( + "hibernate.physical_naming_strategy", + SpringPhysicalNamingStrategy.class.getName()); + } + + @Test + public void hibernate5CustomNamingStrategies() throws Exception { + JpaProperties properties = load(HibernateVersion.V5, + "spring.jpa.hibernate.naming.implicit-strategy:com.example.Implicit", + "spring.jpa.hibernate.naming.physical-strategy:com.example.Physical"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties).contains( + entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), + entry("hibernate.physical_naming_strategy", "com.example.Physical")); + assertThat(hibernateProperties) + .doesNotContainKeys("hibernate.ejb.naming_strategy"); + } + + @Test + public void hibernate5CustomNamingStrategiesViaJpaProperties() throws Exception { + JpaProperties properties = load(HibernateVersion.V5, + "spring.jpa.properties.hibernate.implicit_naming_strategy:com.example.Implicit", + "spring.jpa.properties.hibernate.physical_naming_strategy:com.example.Physical"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + // You can override them as we don't provide any default + assertThat(hibernateProperties).contains( + entry("hibernate.implicit_naming_strategy", "com.example.Implicit"), + entry("hibernate.physical_naming_strategy", "com.example.Physical")); + assertThat(hibernateProperties) + .doesNotContainKeys("hibernate.ejb.naming_strategy"); + } + + @Test + public void useNewIdGeneratorMappingsDefaultHibernate4() throws Exception { + JpaProperties properties = load(HibernateVersion.V4); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties) + .doesNotContainKey(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS); + } + + @Test + public void useNewIdGeneratorMappingsDefaultHibernate5() throws Exception { + JpaProperties properties = load(HibernateVersion.V5); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties) + .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "false"); + } + + @Test + public void useNewIdGeneratorMappingsTrue() throws Exception { + JpaProperties properties = load(HibernateVersion.V5, + "spring.jpa.hibernate.use-new-id-generator-mappings:true"); + Map hibernateProperties = properties + .getHibernateProperties(mockStandaloneDataSource()); + assertThat(hibernateProperties) + .containsEntry(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true"); + } + + @SuppressWarnings("unchecked") + private DataSource mockStandaloneDataSource() throws SQLException { + DataSource ds = mock(DataSource.class); + given(ds.getConnection()).willThrow(SQLException.class); + return ds; + } + + private JpaProperties load(HibernateVersion hibernateVersion, String... environment) { + HibernateVersion.setRunning(hibernateVersion); + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + ctx.register(TestConfiguration.class); + ctx.refresh(); + this.context = ctx; + JpaProperties properties = this.context.getBean(JpaProperties.class); + return properties; + } + + @Configuration + @EnableConfigurationProperties(JpaProperties.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java index 1f808274f8..4708c9e6e6 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/test/City.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index b542b5a42f..14eb91ab0f 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -65,6 +65,7 @@ 2.0.0 1.3.2 10.12.1.1 + 1.6.1 3.1.2 2.10.1 1.50.2 @@ -79,7 +80,7 @@ 1.4.191 1.3 3.6.1 - 4.3.11.Final + 5.1.0.Final 5.2.4.Final 2.4.5 2.3.13 @@ -806,6 +807,11 @@ de.flapdoodle.embed.mongo ${embedded-mongo.version} + + dom4j + dom4j + ${dom4j.version} + io.dropwizard.metrics metrics-core diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java index 5a1bc70180..7ab2e36855 100644 --- a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java +++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java @@ -16,7 +16,9 @@ package org.springframework.boot.orm.jpa.hibernate; +import org.hibernate.Hibernate; import org.hibernate.cfg.ImprovedNamingStrategy; +import org.hibernate.cfg.NamingStrategy; import org.hibernate.internal.util.StringHelper; import org.springframework.util.Assert; @@ -31,7 +33,10 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @see "http://stackoverflow.com/questions/7689206/ejb3namingstrategy-vs-improvednamingstrategy-foreign-key-naming" * @since 1.2.0 + * @deprecated since 1.4.0 since {@link NamingStrategy} is no longer used by + * {@link Hibernate}. Consider using {@link SpringPhysicalNamingStrategy} */ +@Deprecated @SuppressWarnings("serial") public class SpringNamingStrategy extends ImprovedNamingStrategy { diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.java new file mode 100644 index 0000000000..927f02de94 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2016 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.orm.jpa.hibernate; + +import java.util.Locale; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; + +/** + * Hibernate {@link PhysicalNamingStrategy} that follows Spring recommended naming + * conventions. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class SpringPhysicalNamingStrategy implements PhysicalNamingStrategy { + + @Override + public Identifier toPhysicalCatalogName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalSchemaName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalTableName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalSequenceName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + @Override + public Identifier toPhysicalColumnName(Identifier name, + JdbcEnvironment jdbcEnvironment) { + return apply(name); + } + + private Identifier apply(Identifier name) { + if (name == null) { + return null; + } + StringBuilder text = new StringBuilder(name.getText().replace('.', '_')); + for (int i = 1; i < text.length() - 1; i++) { + if (isDashRequired(text.charAt(i - 1), text.charAt(i), text.charAt(i + 1))) { + text.insert(i++, '_'); + } + } + return new Identifier(text.toString().toLowerCase(Locale.ROOT), name.isQuoted()); + } + + private boolean isDashRequired(char before, char current, char after) { + return Character.isLowerCase(before) && Character.isUpperCase(current) + && Character.isLowerCase(after); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategyTests.java b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategyTests.java new file mode 100644 index 0000000000..0ce35b59a2 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategyTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2016 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.orm.jpa.hibernate; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.service.ServiceRegistry; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringPhysicalNamingStrategy}. + * + * @author Phillip Webb + */ +public class SpringPhysicalNamingStrategyTests { + + private Metadata metadata; + + @Before + public void setup() throws Exception { + MetadataSources metadataSources = new MetadataSources(); + metadataSources.addAnnotatedClass(TelephoneNumber.class); + StandardServiceRegistry serviceRegistry = getServiceRegistry(metadataSources); + this.metadata = metadataSources.getMetadataBuilder(serviceRegistry) + .applyPhysicalNamingStrategy(new SpringPhysicalNamingStrategy()).build(); + } + + private StandardServiceRegistry getServiceRegistry(MetadataSources metadataSources) { + ServiceRegistry registry = metadataSources.getServiceRegistry(); + return new StandardServiceRegistryBuilder((BootstrapServiceRegistry) registry) + .applySetting(AvailableSettings.DIALECT, H2Dialect.class).build(); + } + + @Test + public void tableNameShouldBeLowercaseUnderscore() throws Exception { + PersistentClass binding = this.metadata + .getEntityBinding(TelephoneNumber.class.getName()); + assertThat(binding.getTable().getQuotedName()).isEqualTo("telephone_number"); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/TelephoneNumber.java b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/TelephoneNumber.java new file mode 100644 index 0000000000..1729c912dd --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/hibernate/TelephoneNumber.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2016 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.orm.jpa.hibernate; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * Simple entity used in {@link SpringPhysicalNamingStrategyTests}. + * + * @author Phillip Webb + */ +@Entity +public class TelephoneNumber { + + @Id + @GeneratedValue + Long id; + + String areaCode; + + String number; + +}