From 7454c4866b62006f7536476c65eb22aee9adcc51 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 15 May 2014 09:15:05 +0100 Subject: [PATCH] Support for @ConfigurationProperties in JPA Adds JpaProperties to bind to spring.jpa.* (making those properties easier to reason about and visible in the /configprops endpoint). Also allows easy configuration of multiple EntityManagerFactories via new EntityManagerFactoryBuilder. JpaBaseConfiguration has a @Bean of that type so users can inject it to create new or additional EntityManagerFactories. This also simplifies the Hibernate autoconfiguration. Also renames the DataSourceFactory to DataSourceBuilder (since that's what it is). --- .../jdbc/DataSourceAutoConfiguration.java | 4 +- ...rceFactory.java => DataSourceBuilder.java} | 22 +-- .../orm/jpa/EntityManagerFactoryBuilder.java | 163 ++++++++++++++++++ .../jpa/HibernateJpaAutoConfiguration.java | 40 ----- .../orm/jpa/JpaBaseConfiguration.java | 74 ++++---- .../autoconfigure/orm/jpa/JpaProperties.java | 157 +++++++++++++++++ .../boot/autoconfigure/AdhocTestSuite.java | 3 +- .../CommonsDataSourceConfigurationTests.java | 2 +- .../HikariDataSourceConfigurationTests.java | 2 +- .../TomcatDataSourceConfigurationTests.java | 2 +- .../HibernateJpaAutoConfigurationTests.java | 19 +- spring-boot-docs/src/main/asciidoc/howto.adoc | 95 +++++++++- .../main/asciidoc/spring-boot-features.adoc | 2 +- .../spring-boot-starter-parent/pom.xml | 2 +- 14 files changed, 478 insertions(+), 109 deletions(-) rename spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/{DataSourceFactory.java => DataSourceBuilder.java} (86%) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index 266fcecd63..d97e906601 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -154,7 +154,7 @@ public class DataSourceAutoConfiguration { @Bean public DataSource dataSource() { // @formatter:off - DataSourceFactory factory = DataSourceFactory + DataSourceBuilder factory = DataSourceBuilder .create(this.properties.getClassLoader()) .driverClassName(this.properties.getDriverClassName()) .url(this.properties.getUrl()) @@ -209,7 +209,7 @@ public class DataSourceAutoConfiguration { * the driver class can actually be loaded by the data source. */ private ClassLoader getDataSourceClassLoader(ConditionContext context) { - Class dataSourceClass = new DataSourceFactory(context.getClassLoader()) + Class dataSourceClass = new DataSourceBuilder(context.getClassLoader()) .findType(); if (dataSourceClass == null) { return null; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java similarity index 86% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java index 139691b5f3..def4184327 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java @@ -38,7 +38,7 @@ import org.springframework.util.ClassUtils; * * @author Dave Syer */ -public class DataSourceFactory { +public class DataSourceBuilder { private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { "org.apache.tomcat.jdbc.pool.DataSource", @@ -51,15 +51,15 @@ public class DataSourceFactory { private Map properties = new HashMap(); - public static DataSourceFactory create() { - return new DataSourceFactory(null); + public static DataSourceBuilder create() { + return new DataSourceBuilder(null); } - public static DataSourceFactory create(ClassLoader classLoader) { - return new DataSourceFactory(classLoader); + public static DataSourceBuilder create(ClassLoader classLoader) { + return new DataSourceBuilder(classLoader); } - public DataSourceFactory(ClassLoader classLoader) { + public DataSourceBuilder(ClassLoader classLoader) { this.classLoader = classLoader; } @@ -82,27 +82,27 @@ public class DataSourceFactory { return new MutablePropertyValues(this.properties); } - public DataSourceFactory type(Class type) { + public DataSourceBuilder type(Class type) { this.type = type; return this; } - public DataSourceFactory url(String url) { + public DataSourceBuilder url(String url) { this.properties.put("url", url); return this; } - public DataSourceFactory driverClassName(String driverClassName) { + public DataSourceBuilder driverClassName(String driverClassName) { this.properties.put("driverClassName", driverClassName); return this; } - public DataSourceFactory username(String username) { + public DataSourceBuilder username(String username) { this.properties.put("username", username); return this; } - public DataSourceFactory password(String password) { + public DataSourceBuilder password(String password) { this.properties.put("password", password); return this; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java new file mode 100644 index 0000000000..7933bc3c62 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-2013 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.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.sql.DataSource; + +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; +import org.springframework.util.ClassUtils; + +/** + * Convenient builder for JPA EntityManagerFactory instances. Collects common + * configuration when constructed and then allows you to create one or more + * {@link LocalContainerEntityManagerFactoryBean} through a fluent builder pattern. The + * most common options are covered in the builder, but you can always manipulate the + * product of the builder if you need more control, before returning it from a + * @Bean definition. + * + * @author Dave Syer + */ +public class EntityManagerFactoryBuilder { + + private JpaVendorAdapter jpaVendorAdapter; + + private PersistenceUnitManager persistenceUnitManager; + + private JpaProperties properties; + + /** + * Create a new instance passing in the common pieces that will be shared if multiple + * EntityManagerFactory instances are created. + * + * @param jpaVendorAdapter a vendor adapter + * @param properties common configuration options, including generic map for JPA + * vendor properties + * @param persistenceUnitManager optional source of persistence unit information (can + * be null) + */ + public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, + JpaProperties properties, PersistenceUnitManager persistenceUnitManager) { + this.jpaVendorAdapter = jpaVendorAdapter; + this.persistenceUnitManager = persistenceUnitManager; + this.properties = properties; + } + + public Builder dataSource(DataSource dataSource) { + return new Builder(dataSource); + } + + /** + * A fluent builder for a LocalContainerEntityManagerFactoryBean. + * + * @author Dave Syer + */ + public class Builder { + + private DataSource dataSource; + + private String[] packagesToScan; + + private String persistenceUnit; + + private Builder(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * The names of packages to scan for @Entity annotations. + * + * @param packagesToScan packages to scan + * @return the builder for fluent usage + */ + public Builder packages(String... packagesToScan) { + this.packagesToScan = packagesToScan; + return this; + } + + /** + * The classes whose packages should be scanned for @Entity + * annotations. + * + * @param basePackageClasses the classes to use + * @return the builder for fluent usage + */ + public Builder packages(Class... basePackageClasses) { + Set packages = new HashSet(); + for (Class type : basePackageClasses) { + packages.add(ClassUtils.getPackageName(type)); + } + this.packagesToScan = packages.toArray(new String[0]); + return this; + } + + /** + * The name of the persistence unit. If only building one EntityManagerFactory you + * can omit this, but if there are more than one in the same application you + * should give them distinct names. + * + * @param persistenceUnit the name of the persistence unit + * @return the builder for fluent usage + */ + public Builder persistenceUnit(String persistenceUnit) { + this.persistenceUnit = persistenceUnit; + return this; + } + + /** + * Generic properties for standard JPA or vendor-specific configuration. These + * properties override any values provided in the {@link JpaProperties} used to + * create the builder. + * + * @param properties the properties to use + * @return the builder for fluent usage + */ + public Builder properties(Map properties) { + EntityManagerFactoryBuilder.this.properties.getProperties() + .putAll(properties); + return this; + } + + public LocalContainerEntityManagerFactoryBean build() { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); + if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) { + entityManagerFactoryBean + .setPersistenceUnitManager(EntityManagerFactoryBuilder.this.persistenceUnitManager); + } + if (this.persistenceUnit != null) { + entityManagerFactoryBean.setPersistenceUnitName(this.persistenceUnit); + } + entityManagerFactoryBean + .setJpaVendorAdapter(EntityManagerFactoryBuilder.this.jpaVendorAdapter); + entityManagerFactoryBean.setDataSource(this.dataSource); + entityManagerFactoryBean.setPackagesToScan(this.packagesToScan); + entityManagerFactoryBean.getJpaPropertyMap().putAll( + EntityManagerFactoryBuilder.this.properties.getProperties()); + entityManagerFactoryBean.getJpaPropertyMap().putAll( + EntityManagerFactoryBuilder.this.properties + .getHibernateProperties(this.dataSource)); + return entityManagerFactoryBean; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index 9fb3f14049..2d059bb912 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -16,10 +16,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; -import java.util.Map; - import javax.persistence.EntityManager; -import javax.sql.DataSource; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -27,14 +24,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition; -import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.boot.orm.jpa.SpringNamingStrategy; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; @@ -54,44 +47,11 @@ import org.springframework.util.ClassUtils; @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { - private RelaxedPropertyResolver environment; - - public HibernateJpaAutoConfiguration() { - this.environment = null; - } - - @Override - public void setEnvironment(Environment environment) { - super.setEnvironment(environment); - this.environment = new RelaxedPropertyResolver(environment, - "spring.jpa.hibernate."); - } - @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } - @Override - protected void configure( - LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { - Map properties = entityManagerFactoryBean.getJpaPropertyMap(); - properties.put("hibernate.ejb.naming_strategy", this.environment.getProperty( - "naming-strategy", SpringNamingStrategy.class.getName())); - String ddlAuto = this.environment.getProperty("ddl-auto", - getDefaultDdlAuto(entityManagerFactoryBean.getDataSource())); - if (!"none".equals(ddlAuto)) { - properties.put("hibernate.hbm2ddl.auto", ddlAuto); - } - } - - private String getDefaultDdlAuto(DataSource dataSource) { - if (EmbeddedDatabaseConnection.isEmbedded(dataSource)) { - return "create-drop"; - } - return "none"; - } - static class HibernateEntityManagerCondition extends SpringBootCondition { private static String[] CLASS_NAMES = { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index 10114be5d3..4d30acaf37 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -30,11 +30,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.context.EnvironmentAware; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; +import org.springframework.context.annotation.Primary; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -42,7 +41,6 @@ import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; -import org.springframework.orm.jpa.vendor.Database; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -54,19 +52,19 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter * @author Dave Syer * @author Oliver Gierke */ -public abstract class JpaBaseConfiguration implements BeanFactoryAware, EnvironmentAware { +@EnableConfigurationProperties(JpaProperties.class) +public abstract class JpaBaseConfiguration implements BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; - private RelaxedPropertyResolver environment; + @Autowired + private DataSource dataSource; @Autowired(required = false) private PersistenceUnitManager persistenceUnitManager; - @Override - public void setEnvironment(Environment environment) { - this.environment = new RelaxedPropertyResolver(environment, "spring.jpa."); - } + @Autowired + private JpaProperties jpaProperties; @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) @@ -75,47 +73,35 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware, Environm } @Bean - @ConditionalOnMissingBean(name = "entityManagerFactory") - public LocalContainerEntityManagerFactoryBean entityManagerFactory( - JpaVendorAdapter jpaVendorAdapter) { - LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); - if (this.persistenceUnitManager != null) { - entityManagerFactoryBean - .setPersistenceUnitManager(this.persistenceUnitManager); - } - entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter); - entityManagerFactoryBean.setDataSource(getDataSource()); - entityManagerFactoryBean.setPackagesToScan(getPackagesToScan()); - entityManagerFactoryBean.getJpaPropertyMap().putAll( - this.environment.getSubProperties("properties.")); - configure(entityManagerFactoryBean); - return entityManagerFactoryBean; - } - - @Bean - @ConditionalOnMissingBean(JpaVendorAdapter.class) + @ConditionalOnMissingBean public JpaVendorAdapter jpaVendorAdapter() { AbstractJpaVendorAdapter adapter = createJpaVendorAdapter(); - adapter.setShowSql(this.environment.getProperty("show-sql", Boolean.class, true)); - adapter.setDatabasePlatform(this.environment.getProperty("database-platform")); - adapter.setDatabase(this.environment.getProperty("database", Database.class, - Database.DEFAULT)); - adapter.setGenerateDdl(this.environment.getProperty("generate-ddl", - Boolean.class, false)); + adapter.setShowSql(this.jpaProperties.isShowSql()); + adapter.setDatabase(this.jpaProperties.getDatabase()); + adapter.setDatabasePlatform(this.jpaProperties.getDatabasePlatform()); + adapter.setGenerateDdl(this.jpaProperties.isGenerateDdl()); return adapter; } - protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter(); - - protected DataSource getDataSource() { - try { - return this.beanFactory.getBean("dataSource", DataSource.class); - } - catch (RuntimeException ex) { - return this.beanFactory.getBean(DataSource.class); - } + @Bean + @ConditionalOnMissingBean + public EntityManagerFactoryBuilder entityManagerFactoryBuilder( + JpaVendorAdapter jpaVendorAdapter) { + EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder( + jpaVendorAdapter, this.jpaProperties, this.persistenceUnitManager); + return builder; } + @Bean + @Primary + @ConditionalOnMissingBean + public LocalContainerEntityManagerFactoryBean entityManagerFactory( + EntityManagerFactoryBuilder factory) { + return factory.dataSource(this.dataSource).packages(getPackagesToScan()).build(); + } + + protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter(); + protected String[] getPackagesToScan() { List basePackages = AutoConfigurationPackages.get(this.beanFactory); return basePackages.toArray(new String[basePackages.size()]); 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 new file mode 100644 index 0000000000..73abbb8bd4 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -0,0 +1,157 @@ +/* + * Copyright 2012-2013 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.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.SpringNamingStrategy; +import org.springframework.orm.jpa.vendor.Database; + +/** + * External configuration properties for a JPA EntityManagerFactory created by Spring. + * + * @author Dave Syer + */ +@ConfigurationProperties(prefix = "spring.jpa") +public class JpaProperties { + + private Map properties = new HashMap(); + + private String databasePlatform; + + private Database database = Database.DEFAULT; + + private boolean generateDdl = false; + + private boolean showSql = false; + + private Hibernate hibernate = new Hibernate(); + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public String getDatabasePlatform() { + return this.databasePlatform; + } + + public void setDatabasePlatform(String databasePlatform) { + this.databasePlatform = databasePlatform; + } + + public Database getDatabase() { + return this.database; + } + + public void setDatabase(Database database) { + this.database = database; + } + + public boolean isGenerateDdl() { + return this.generateDdl; + } + + public void setGenerateDdl(boolean generateDdl) { + this.generateDdl = generateDdl; + } + + public boolean isShowSql() { + return this.showSql; + } + + public void setShowSql(boolean showSql) { + this.showSql = showSql; + } + + public Hibernate getHibernate() { + return this.hibernate; + } + + public void setHibernate(Hibernate hibernate) { + this.hibernate = hibernate; + } + + public Map getHibernateProperties(DataSource dataSource) { + return this.hibernate.getAdditionalProperties(this.properties, dataSource); + } + + public static class Hibernate { + + private Class namingStrategy; + + private static Class DEFAULT_NAMING_STRATEGY = SpringNamingStrategy.class; + + private String ddlAuto; + + public Class getNamingStrategy() { + return this.namingStrategy; + } + + public void setNamingStrategy(Class namingStrategy) { + this.namingStrategy = namingStrategy; + } + + public String getDdlAuto() { + return this.ddlAuto; + } + + public void setDdlAuto(String ddlAuto) { + this.ddlAuto = ddlAuto; + } + + private Map getAdditionalProperties(Map existing, + DataSource dataSource) { + Map result = new HashMap(); + if (!isAlreadyProvided(existing, "ejb.naming_strategy") + && this.namingStrategy != null) { + result.put("hibernate.ejb.naming_strategy", this.namingStrategy.getName()); + } + else if (this.namingStrategy == null) { + result.put("hibernate.ejb.naming_strategy", + DEFAULT_NAMING_STRATEGY.getName()); + } + String ddlAuto = this.ddlAuto != null ? this.ddlAuto + : getDefaultDdlAuto(dataSource); + if (!isAlreadyProvided(existing, "hbm2ddl.auto") && !"none".equals(ddlAuto)) { + result.put("hibernate.hbm2ddl.auto", ddlAuto); + } + return result; + } + + private boolean isAlreadyProvided(Map existing, String key) { + return existing.containsKey("hibernate." + key); + } + + private String getDefaultDdlAuto(DataSource dataSource) { + if (EmbeddedDatabaseConnection.isEmbedded(dataSource)) { + return "create-drop"; + } + return "none"; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AdhocTestSuite.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AdhocTestSuite.java index 944ed6fcb1..6d8dfdca3c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AdhocTestSuite.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AdhocTestSuite.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure; +import org.junit.Ignore; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @@ -30,7 +31,7 @@ import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurat @RunWith(Suite.class) @SuiteClasses({ HibernateJpaAutoConfigurationTests.class, LiquibaseAutoConfigurationTests.class }) -// @Ignore +@Ignore public class AdhocTestSuite { } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java index a94fe4841a..77a5e25792 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java @@ -94,7 +94,7 @@ public class CommonsDataSourceConfigurationTests { @Bean @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) public DataSource dataSource() { - return DataSourceFactory.create().type(BasicDataSource.class).build(); + return DataSourceBuilder.create().type(BasicDataSource.class).build(); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java index 7afe94aec9..d564a0e4b7 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java @@ -105,7 +105,7 @@ public class HikariDataSourceConfigurationTests { @Bean @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) public DataSource dataSource() { - return DataSourceFactory.create().type(HikariDataSource.class).build(); + return DataSourceBuilder.create().type(HikariDataSource.class).build(); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java index 5820a20cc3..3cd8dfefa3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java @@ -137,7 +137,7 @@ public class TomcatDataSourceConfigurationTests { @Bean @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) public DataSource dataSource() { - return DataSourceFactory.create() + return DataSourceBuilder.create() .type(org.apache.tomcat.jdbc.pool.DataSource.class).build(); } 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 f3a987231a..cd05fce0da 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 @@ -21,6 +21,7 @@ import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; /** @@ -39,7 +40,7 @@ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigura @Test public void testCustomNamingStrategy() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, - "spring.jpa.hibernate.namingstrategy:" + "spring.jpa.hibernate.namingStrategy:" + "org.hibernate.cfg.EJB3NamingStrategy"); setupTestConfiguration(); this.context.refresh(); @@ -50,4 +51,20 @@ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigura assertThat(actual, equalTo("org.hibernate.cfg.EJB3NamingStrategy")); } + @Test + public void testCustomNamingStrategyViaJpaProperties() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jpa.properties.hibernate.ejb.naming_strategy:" + + "org.hibernate.cfg.EJB3NamingStrategy"); + setupTestConfiguration(); + this.context.refresh(); + LocalContainerEntityManagerFactoryBean bean = this.context + .getBean(LocalContainerEntityManagerFactoryBean.class); + String actual = (String) bean.getJpaPropertyMap().get( + "hibernate.ejb.naming_strategy"); + // You can't override this one from spring.jpa.properties because it has an + // opinionated default + assertThat(actual, not(equalTo("org.hibernate.cfg.EJB3NamingStrategy"))); + } + } diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 0265f0389c..ba4707f857 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -925,12 +925,55 @@ action. [[howto-configure-a-datasource]] === Configure a DataSource -To override the default settings just define a `@Bean` of your own of type `DataSource`. +To override the default settings just define a `@Bean` of your own of type `DataSource`. +Spring Boot provides a utility builder class `DataSourceBuilder` that can be used +to create one of the standard ones (if it is on the classpath), or you can just create +your own, and bind it to a set of `Environment` properties e.g. + +[source,java,indent=0,subs="verbatim,quotes,attributes"] +---- + @Bean + @ConfigurationProperties(prefix="datasource.mine") + public DataSource dataSource() { + return new FancyDataSource(); + } +---- + +[source,properties,indent=0] +---- + datasource.mine.jdbcUrl=jdbc:h2:mem:mydb + datasource.mine.user=sa + datasource.mine.poolSize=30 +---- + + See '<>' in the ``Spring Boot features'' section and the {sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[`DataSourceAutoConfiguration`] class for more details. +[[howto-two-datasources]] +=== Configure Two DataSources +Creating more than one data source works the same as creating the first one. +You might want to mark one of them as `@Primary` if you are using the default +auto-configuration for JDBC or JPA (then that one will be picked up by any +`@Autowired` injections). + +[source,java,indent=0,subs="verbatim,quotes,attributes"] +---- + @Bean + @Primary + @ConfigurationProperties(prefix="datasource.primary") + public DataSource primaryDataSource() { + return new FancyDataSource(); + } + + @Bean + @ConfigurationProperties(prefix="datasource.secondary") + public DataSource secondaryDataSource() { + return new FancyDataSource(); + } +---- [[howto-use-spring-data-repositories]] @@ -998,15 +1041,57 @@ and {sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[`JpaBas for more details. - [[howto-use-custom-entity-manager]] === Use a custom EntityManagerFactory To take full control of the configuration of the `EntityManagerFactory`, you need to add -a `@Bean` named "entityManagerFactory". To avoid eager initialization of JPA -infrastructure, Spring Boot auto-configuration does not switch on its entity manager -based on the presence of a bean of that type. Instead it has to do it by name. +a `@Bean` named "entityManagerFactory". Spring Boot auto-configuration switches off its entity manager +based on the presence of a bean of that type. +[[howto-use-two-entity-managers]] +=== Use Two EntityManagers + +Even if the default `EntityManagerFactory` works fine, you will need +to define a new one because otherwise the presence of the second bean +of that type will switch off the default. To make it easy to do that +you can use the convenient `EntityManagerBuilder` provided by Spring +Boot, or if you prefer you can just use the +`LocalContainerEntityManagerFactoryBean` directly from Spring ORM. +Example: + +[source,java,indent=0,subs="verbatim,quotes,attributes"] +---- + // add two data sources configured as above + + @Bean + public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory( + EntityManagerFactoryBuilder builder) { + return builder + .dataSource(customerDataSource()) + .packages(Customer.class) + .persistenceUnit("customers") + .build(); + } + + @Bean + public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory( + EntityManagerFactoryBuilder builder) { + return builder + .dataSource(orderDataSource()) + .packages(Order.class) + .persistenceUnit("orders") + .build(); + } + +---- + +The configuration above almost works on its own. To complete the +picture you need to configure `TransactionManagers` for the two +`EntityManagers` as well. One of them could be picked up by the +default `JpaTransactionManager` in Spring Boot if you mark it as +`@Primary`. The other would have to be explicitly injected into a new +instance. Or you might be able to use a JTA transaction manager +spanning both. [[howto-use-traditional-persistence-xml]] === Use a traditional persistence.xml diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 7b059968bb..a1c715c208 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1392,7 +1392,7 @@ following to your `application.properties`. NOTE: Hibernate's own internal property name for this (if you happen to remember it better) is `hibernate.hbm2ddl.auto`. You can set it, along with other Hibernate native properties, using `spring.jpa.properties.*` (the prefix is stripped before adding them -to the entity manager). Alternatively, `spring.jpa.generate-ddl=false` switches off all +to the entity manager). Also, `spring.jpa.generate-ddl=false` switches off all DDL generation. diff --git a/spring-boot-starters/spring-boot-starter-parent/pom.xml b/spring-boot-starters/spring-boot-starter-parent/pom.xml index 2d3b4ba3c3..35a39d6934 100644 --- a/spring-boot-starters/spring-boot-starter-parent/pom.xml +++ b/spring-boot-starters/spring-boot-starter-parent/pom.xml @@ -127,7 +127,7 @@ true yyyy-MM-dd'T'HH:mm:ssZ true - src/main/resources/git.properties + ${basedir}/src/main/resources/git.properties