From 8219f2be4c0607fcace370c1de685f953dcecb4b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 22 Aug 2014 12:10:58 -0700 Subject: [PATCH] Update DataSource auto-configuration to support XA Update DataSource and JPA auto-configuration to consider XA datasources. See gh-947 --- .../jdbc/DataSourceAutoConfiguration.java | 9 +- .../autoconfigure/jdbc/DataSourceBuilder.java | 8 +- .../jdbc/DataSourceProperties.java | 44 +++++- .../autoconfigure/jdbc/DatabaseDriver.java | 137 ++++++++++++++++++ .../jdbc/JndiDataSourceAutoConfiguration.java | 3 +- .../jdbc/XADataSourceAutoConfiguration.java | 119 +++++++++++++++ .../orm/jpa/EntityManagerFactoryBuilder.java | 26 +++- .../jpa/HibernateJpaAutoConfiguration.java | 28 +++- .../orm/jpa/JpaBaseConfiguration.java | 30 +++- .../main/resources/META-INF/spring.factories | 1 + ...derTests.java => DatabaseDriverTests.java} | 29 ++-- .../XADataSourceAutoConfigurationTests.java | 128 ++++++++++++++++ 12 files changed, 528 insertions(+), 34 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java rename spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/{DriverClassNameProviderTests.java => DatabaseDriverTests.java} (63%) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.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 e07915bb241..5a68991a21c 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 @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.jdbc; import javax.sql.DataSource; +import javax.sql.XADataSource; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -74,7 +75,7 @@ public class DataSourceAutoConfiguration { } @Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class) - @ConditionalOnMissingBean(DataSource.class) + @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedConfiguration { @@ -92,7 +93,7 @@ public class DataSourceAutoConfiguration { } @Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class) - @ConditionalOnMissingBean(DataSource.class) + @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) protected static class NonEmbeddedConfiguration { @Autowired @@ -196,7 +197,8 @@ public class DataSourceAutoConfiguration { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (hasBean(context, DataSource.class)) { + if (hasBean(context, DataSource.class) + || hasBean(context, XADataSource.class)) { return ConditionOutcome .match("existing bean configured database detected"); } @@ -210,6 +212,7 @@ public class DataSourceAutoConfiguration { return BeanFactoryUtils.beanNamesForTypeIncludingAncestors( context.getBeanFactory(), type, true, false).length > 0; } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java index cecc04a3c0f..18d13e3f28e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java @@ -49,8 +49,6 @@ public class DataSourceBuilder { private ClassLoader classLoader; - private DriverClassNameProvider driverClassNameProvider = new DriverClassNameProvider(); - private Map properties = new HashMap(); public static DataSourceBuilder create() { @@ -76,9 +74,9 @@ public class DataSourceBuilder { private void maybeGetDriverClassName() { if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) { - String cls = this.driverClassNameProvider.getDriverClassName(this.properties - .get("url")); - this.properties.put("driverClassName", cls); + String url = this.properties.get("url"); + String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName(); + this.properties.put("driverClassName", driverClass); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index e4973992850..fb8ee46c7e0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -16,6 +16,9 @@ package org.springframework.boot.autoconfigure.jdbc; +import java.util.LinkedHashMap; +import java.util.Map; + import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean; @@ -64,7 +67,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE; - private DriverClassNameProvider driverClassNameProvider = new DriverClassNameProvider(); + private Xa xa = new Xa(); @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -86,7 +89,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB String driverClassName = null; if (StringUtils.hasText(this.url)) { - driverClassName = this.driverClassNameProvider.getDriverClassName(this.url); + driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName(); } if (!StringUtils.hasText(driverClassName)) { @@ -113,7 +116,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB "Cannot determine embedded database url for database type " + this.embeddedDatabaseConnection + ". If you want an embedded " - + "database please put a supported on on the classpath."); + + "database please put a supported one on the classpath."); } return url; } @@ -228,4 +231,39 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB return this.classLoader; } + public Xa getXa() { + return this.xa; + } + + public void setXa(Xa xa) { + this.xa = xa; + } + + /** + * XA Specific datasource settings. + */ + public static class Xa { + + private String dataSourceClassName; + + private Map properties = new LinkedHashMap(); + + public String getDataSourceClassName() { + return this.dataSourceClassName; + } + + public void setDataSourceClassName(String dataSourceClassName) { + this.dataSourceClassName = dataSourceClassName; + } + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java new file mode 100644 index 00000000000..026607762fc --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java @@ -0,0 +1,137 @@ +/* + * Copyright 2012-2014 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.jdbc; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Enumeration of common database drivers. + * + * @author Phillip Webb + * @author Maciej Walkowiak + * @since 1.2.0 + */ +enum DatabaseDriver { + + /** + * Unknown type. + */ + UNKNOWN(null), + + /** + * Apache Derby. + */ + DERBY("org.apache.derby.jdbc.EmbeddedDriver"), + + /** + * H2. + */ + H2("org.h2.Driver", "org.h2.jdbcx.JdbcDataSource"), + + /** + * HyperSQL DataBase. + */ + HSQLDB("org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.pool.JDBCXADataSource"), + + /** + * SQL Lite. + */ + SQLITE("org.sqlite.JDBC"), + + /** + * MySQL. + */ + MYSQL("com.mysql.jdbc.Driver", "org.mysql.jdbc.MySQLDataSource"), + + /** + * Maria DB. + */ + MARIADB("org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MySQLDataSource"), + + /** + * Google App Engine. + */ + GOOGLE("com.google.appengine.api.rdbms.AppEngineDriver"), + + /** + * Oracle + */ + ORACLE("oracle.jdbc.OracleDriver", "oracle.jdbc.xa.OracleXADataSource"), + + /** + * Postres + */ + POSTGRESQL("org.postgresql.Driver", "org.postgresql.xa.PGXADataSource"), + + /** + * JTDS + */ + JTDS("net.sourceforge.jtds.jdbc.Driver"), + + /** + * SQL Server + */ + SQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + + private final String driverClassName; + + private final String xaDataSourceClassName; + + private DatabaseDriver(String driverClassName) { + this(driverClassName, null); + } + + private DatabaseDriver(String driverClassName, String xaDataSourceClassName) { + this.driverClassName = driverClassName; + this.xaDataSourceClassName = xaDataSourceClassName; + } + + /** + * @return the driverClassName or {@code null} + */ + public String getDriverClassName() { + return this.driverClassName; + } + + /** + * @return the xaDataSourceClassName or {@code null} + */ + public String getXaDataSourceClassName() { + return this.xaDataSourceClassName; + } + + /** + * Find a {@link DatabaseDriver} for the given URL. + * @param url JDBC URL + * @return driver class name or {@link #UNKNOWN} if not found + */ + public static DatabaseDriver fromJdbcUrl(String url) { + if (StringUtils.hasLength(url)) { + Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'"); + String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(); + for (DatabaseDriver driver : values()) { + String prefix = ":" + driver.name().toLowerCase() + ":"; + if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) { + return driver; + } + } + } + return UNKNOWN; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java index 735ab893572..2308938aea8 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java @@ -36,7 +36,8 @@ import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; * @since 1.2.0 */ @Configuration -@AutoConfigureBefore(DataSourceAutoConfiguration.class) +@AutoConfigureBefore({ XADataSourceAutoConfiguration.class, + DataSourceAutoConfiguration.class }) @ConditionalOnClass(DataSource.class) @ConditionalOnProperty(prefix = DataSourceProperties.PREFIX, name = "jndi-name") @EnableConfigurationProperties(DataSourceProperties.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java new file mode 100644 index 00000000000..1f5c00b844d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2014 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.jdbc; + +import javax.sql.DataSource; +import javax.sql.XADataSource; +import javax.transaction.TransactionManager; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration; +import org.springframework.boot.bind.RelaxedDataBinder; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jta.XADataSourceWrapper; +import org.springframework.context.annotation.Bean; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource} with XA. + * + * @author Phillip Webb + * @author Josh Long + * @since 1.2.0 + */ +@AutoConfigureBefore(DataSourceAutoConfiguration.class) +@AutoConfigureAfter(JtaAutoConfiguration.class) +@EnableConfigurationProperties(DataSourceProperties.class) +@ConditionalOnClass({ DataSource.class, TransactionManager.class }) +@ConditionalOnBean(XADataSourceWrapper.class) +@ConditionalOnMissingBean(DataSource.class) +public class XADataSourceAutoConfiguration implements BeanClassLoaderAware { + + @Autowired + private XADataSourceWrapper wrapper; + + @Autowired + private DataSourceProperties properties; + + @Autowired(required = false) + private XADataSource xaDataSource; + + private ClassLoader classLoader; + + @Bean + @ConfigurationProperties(prefix = DataSourceProperties.PREFIX) + public DataSource dataSource() throws Exception { + XADataSource xaDataSource = this.xaDataSource; + if (xaDataSource == null) { + xaDataSource = createXaDataSource(); + } + return this.wrapper.wrapDataSource(xaDataSource); + } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + private XADataSource createXaDataSource() { + String className = this.properties.getXa().getDataSourceClassName(); + if (!StringUtils.hasLength(className)) { + className = DatabaseDriver.fromJdbcUrl(this.properties.getUrl()) + .getXaDataSourceClassName(); + } + Assert.state(StringUtils.hasLength(className), + "No XA DataSource class name specified"); + XADataSource dataSource = createXaDataSourceInstance(className); + bindXaProperties(dataSource, this.properties); + return dataSource; + } + + private XADataSource createXaDataSourceInstance(String className) { + try { + Class dataSourceClass = ClassUtils.forName(className, this.classLoader); + Object instance = BeanUtils.instantiate(dataSourceClass); + Assert.isInstanceOf(XADataSource.class, instance); + return (XADataSource) instance; + } + catch (Exception ex) { + throw new IllegalStateException( + "Unable to create XADataSource instance from '" + className + "'"); + } + } + + private void bindXaProperties(XADataSource target, DataSourceProperties properties) { + MutablePropertyValues values = new MutablePropertyValues(); + values.add("user", this.properties.getUsername()); + values.add("password", this.properties.getPassword()); + values.add("url", this.properties.getUrl()); + values.addPropertyValues(properties.getXa().getProperties()); + new RelaxedDataBinder(target).withAlias("user", "username").bind(values); + } + +} 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 index 1432c27d0ff..af710e687d1 100644 --- 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 @@ -89,6 +89,8 @@ public class EntityManagerFactoryBuilder { private Map properties = new HashMap(); + private boolean jta; + private Builder(DataSource dataSource) { this.dataSource = dataSource; } @@ -142,6 +144,21 @@ public class EntityManagerFactoryBuilder { return this; } + /** + * Configure if using a JTA {@link DataSource}, i.e. if + * {@link LocalContainerEntityManagerFactoryBean#setDataSource(DataSource) + * setDataSource} or + * {@link LocalContainerEntityManagerFactoryBean#setJtaDataSource(DataSource) + * setJtaDataSource} should be called on the + * {@link LocalContainerEntityManagerFactoryBean}. + * @param jta if the data source is JTA + * @return the builder for fluent usage + */ + public Builder jta(boolean jta) { + this.jta = jta; + return this; + } + public LocalContainerEntityManagerFactoryBean build() { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) { @@ -153,7 +170,14 @@ public class EntityManagerFactoryBuilder { } entityManagerFactoryBean .setJpaVendorAdapter(EntityManagerFactoryBuilder.this.jpaVendorAdapter); - entityManagerFactoryBean.setDataSource(this.dataSource); + + if (this.jta) { + entityManagerFactoryBean.setJtaDataSource(this.dataSource); + } + else { + entityManagerFactoryBean.setDataSource(this.dataSource); + } + entityManagerFactoryBean.setPackagesToScan(this.packagesToScan); entityManagerFactoryBean.getJpaPropertyMap().putAll( EntityManagerFactoryBuilder.this.properties.getProperties()); 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 2cb64bcbd88..979ca1fa0b6 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 @@ -22,6 +22,7 @@ import java.util.Map; import javax.persistence.EntityManager; import javax.sql.DataSource; +import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -29,8 +30,9 @@ 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.jta.JtaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition; -import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -39,29 +41,30 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.util.ClassUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for Hibernate JPA. * * @author Phillip Webb + * @author Josh Long */ @Configuration @ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EnableTransactionManagement.class, EntityManager.class }) @Conditional(HibernateEntityManagerCondition.class) -@AutoConfigureAfter(DataSourceAutoConfiguration.class) +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, JtaAutoConfiguration.class }) public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { + private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform"; + @Autowired private JpaProperties properties; @Autowired private DataSource dataSource; - @Autowired - private ConfigurableApplicationContext applicationContext; - @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { return new HibernateJpaVendorAdapter(); @@ -74,6 +77,21 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { return vendorProperties; } + @Override + protected void customizeVendorProperties(Map vendorProperties) { + super.customizeVendorProperties(vendorProperties); + if (!vendorProperties.containsKey(JTA_PLATFORM)) { + JtaTransactionManager jtaTransactionManager = getJtaTransactionManager(); + if (jtaTransactionManager != null) { + vendorProperties.put(JTA_PLATFORM, new SpringJtaPlatform( + jtaTransactionManager)); + } + else { + vendorProperties.put(JTA_PLATFORM, NoJtaPlatform.INSTANCE); + } + } + } + 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 0e4c3497a7b..c0935824dff 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 @@ -44,6 +44,7 @@ import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -71,6 +72,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { @Autowired private JpaProperties jpaProperties; + @Autowired(required = false) + private JtaTransactionManager jtaTransactionManager; + @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) public PlatformTransactionManager transactionManager() { @@ -103,14 +107,24 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { @ConditionalOnMissingBean public LocalContainerEntityManagerFactoryBean entityManagerFactory( EntityManagerFactoryBuilder factoryBuilder) { + Map vendorProperties = getVendorProperties(); + customizeVendorProperties(vendorProperties); return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()) - .properties(getVendorProperties()).build(); + .properties(vendorProperties).jta(isJta()).build(); } protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter(); protected abstract Map getVendorProperties(); + /** + * Customize vendor properties before they are used. Allows for post processing (for + * example to configure JTA specific settings). + * @param vendorProperties the vendor properties to customize + */ + protected void customizeVendorProperties(Map vendorProperties) { + } + protected EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback() { return null; } @@ -127,6 +141,20 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { } + /** + * @return the jtaTransactionManager or {@code null} + */ + protected JtaTransactionManager getJtaTransactionManager() { + return this.jtaTransactionManager; + } + + /** + * Returns if a JTA {@link PlatformTransactionManager} is being used. + */ + protected final boolean isJta() { + return (this.jtaTransactionManager != null); + } + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index f2f147692ee..8a3f4a6458f 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -20,6 +20,7 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ +org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DriverClassNameProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java similarity index 63% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DriverClassNameProviderTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java index 861941007cf..53987b2b8c1 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DriverClassNameProviderTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java @@ -20,47 +20,46 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; /** - * Tests for {@link DriverClassNameProvider}. + * Tests for {@link DatabaseDriver}. * + * @author Phillip Webb * @author Maciej Walkowiak */ -public class DriverClassNameProviderTests { - - private DriverClassNameProvider provider = new DriverClassNameProvider(); +public class DatabaseDriverTests { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void classNameForKnownDatabase() { - String driverClassName = this.provider - .getDriverClassName("jdbc:postgresql://hostname/dbname"); + String driverClassName = DatabaseDriver.fromJdbcUrl( + "jdbc:postgresql://hostname/dbname").getDriverClassName(); assertEquals("org.postgresql.Driver", driverClassName); } @Test - public void nullForUnknownDatabase() { - String driverClassName = this.provider - .getDriverClassName("jdbc:unknowndb://hostname/dbname"); + public void nullClassNameForUnknownDatabase() { + String driverClassName = DatabaseDriver.fromJdbcUrl( + "jdbc:unknowndb://hostname/dbname").getDriverClassName(); assertNull(driverClassName); } @Test - public void failureOnNullJdbcUrl() { - this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("JdbcUrl must not be null"); - this.provider.getDriverClassName(null); + public void unknownOnNullJdbcUrl() { + assertThat(DatabaseDriver.fromJdbcUrl(null), equalTo(DatabaseDriver.UNKNOWN)); } @Test public void failureOnMalformedJdbcUrl() { this.thrown.expect(IllegalArgumentException.class); - this.thrown.expectMessage("JdbcUrl must start with"); - this.provider.getDriverClassName("malformed:url"); + this.thrown.expectMessage("URL must start with"); + DatabaseDriver.fromJdbcUrl("malformed:url"); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java new file mode 100644 index 00000000000..0defecd3640 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2014 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.jdbc; + +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import org.hsqldb.jdbc.pool.JDBCXADataSource; +import org.junit.Test; +import org.springframework.boot.jta.XADataSourceWrapper; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link XADataSourceAutoConfiguration}. + * + * @author Phillip Webb + */ +public class XADataSourceAutoConfigurationTests { + + @Test + public void wrapExistingXaDataSource() throws Exception { + ApplicationContext context = createContext(WrapExisting.class); + context.getBean(DataSource.class); + XADataSource source = context.getBean(XADataSource.class); + MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class); + assertThat(wrapper.getXaDataSource(), equalTo(source)); + } + + @Test + public void createFromUrl() throws Exception { + ApplicationContext context = createContext(FromProperties.class, + "spring.datasource.url:jdbc:hsqldb:mem:test", + "spring.datasource.username:un"); + context.getBean(DataSource.class); + MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class); + JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource(); + assertNotNull(dataSource); + assertThat(dataSource.getUrl(), equalTo("jdbc:hsqldb:mem:test")); + assertThat(dataSource.getUser(), equalTo("un")); + } + + @Test + public void createFromClass() throws Exception { + ApplicationContext context = createContext( + FromProperties.class, + "spring.datasource.xa.data-source-class:org.hsqldb.jdbc.pool.JDBCXADataSource", + "spring.datasource.xa.properties.database-name:test"); + context.getBean(DataSource.class); + MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class); + JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource(); + assertNotNull(dataSource); + assertThat(dataSource.getDatabaseName(), equalTo("test")); + + } + + private ApplicationContext createContext(Class configuration, String... env) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(context, env); + context.register(configuration, XADataSourceAutoConfiguration.class); + context.refresh(); + return context; + } + + @Configuration + static class WrapExisting { + + @Bean + public MockXADataSourceWrapper wrapper() { + return new MockXADataSourceWrapper(); + } + + @Bean + public XADataSource xaDataSource() { + return mock(XADataSource.class); + } + + } + + @Configuration + static class FromProperties { + + @Bean + public MockXADataSourceWrapper wrapper() { + return new MockXADataSourceWrapper(); + } + + } + + private static class MockXADataSourceWrapper implements XADataSourceWrapper { + + private XADataSource dataSource; + + @Override + public DataSource wrapDataSource(XADataSource dataSource) { + this.dataSource = dataSource; + return mock(DataSource.class); + } + + public XADataSource getXaDataSource() { + return this.dataSource; + } + + } + +}