diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java index d55074d18a8..db3f4318a55 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java @@ -49,6 +49,7 @@ import com.atomikos.icatch.jta.UserTransactionManager; * * @author Josh Long * @author Phillip Webb + * @author Andy Wilkinson * @since 1.2.0 */ @Configuration @@ -71,6 +72,10 @@ class AtomikosJtaConfiguration { public UserTransactionService userTransactionService( AtomikosProperties atomikosProperties) { Properties properties = new Properties(); + if (StringUtils.hasText(this.jtaProperties.getTransactionManagerId())) { + properties.setProperty("com.atomikos.icatch.tm_unique_name", + this.jtaProperties.getTransactionManagerId()); + } properties.setProperty("com.atomikos.icatch.log_base_dir", getLogBaseDir()); properties.putAll(atomikosProperties.asProperties()); return new UserTransactionServiceImp(properties); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java index dfb674f740b..92e83b86d7c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java @@ -45,6 +45,7 @@ import bitronix.tm.jndi.BitronixContext; * * @author Josh Long * @author Phillip Webb + * @author Andy Wilkinson * @since 1.2.0 */ @Configuration @@ -60,7 +61,9 @@ class BitronixJtaConfiguration { @ConfigurationProperties(prefix = JtaProperties.PREFIX) public bitronix.tm.Configuration bitronixConfiguration() { bitronix.tm.Configuration config = TransactionManagerServices.getConfiguration(); - config.setServerId("spring-boot-jta-bitronix"); + if (StringUtils.hasText(this.jtaProperties.getTransactionManagerId())) { + config.setServerId(this.jtaProperties.getTransactionManagerId()); + } File logBaseDir = getLogBaseDir(); config.setLogPart1Filename(new File(logBaseDir, "part1.btm").getAbsolutePath()); config.setLogPart2Filename(new File(logBaseDir, "part2.btm").getAbsolutePath()); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java index 02fda641e9a..3ab388e783f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java @@ -26,6 +26,7 @@ import org.springframework.transaction.jta.JtaTransactionManager; * * @author Josh Long * @author Phillip Webb + * @author Andy Wilkinson * @since 1.2.0 */ @ConfigurationProperties(prefix = JtaProperties.PREFIX, ignoreUnknownFields = true) @@ -35,6 +36,8 @@ public class JtaProperties { private String logDir; + private String transactionManagerId; + public void setLogDir(String logDir) { this.logDir = logDir; } @@ -43,4 +46,12 @@ public class JtaProperties { return this.logDir; } + public String getTransactionManagerId() { + return this.transactionManagerId; + } + + public void setTransactionManagerId(String transactionManagerId) { + this.transactionManagerId = transactionManagerId; + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java index 72dcec28d03..58395063d45 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java @@ -16,28 +16,41 @@ package org.springframework.boot.autoconfigure.jta; +import java.io.File; +import java.net.InetAddress; +import java.net.UnknownHostException; + import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jta.XAConnectionFactoryWrapper; import org.springframework.boot.jta.XADataSourceWrapper; import org.springframework.boot.jta.atomikos.AtomikosDependsOnBeanFactoryPostProcessor; import org.springframework.boot.jta.atomikos.AtomikosProperties; import org.springframework.boot.jta.bitronix.BitronixDependentBeanFactoryPostProcessor; +import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.jta.JtaTransactionManager; +import org.springframework.util.FileSystemUtils; import com.atomikos.icatch.config.UserTransactionService; import com.atomikos.icatch.jta.UserTransactionManager; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; /** @@ -45,6 +58,7 @@ import static org.mockito.Mockito.mock; * * @author Josh Long * @author Phillip Webb + * @author Andy Wilkinson */ public class JtaAutoConfigurationTests { @@ -53,6 +67,11 @@ public class JtaAutoConfigurationTests { private AnnotationConfigApplicationContext context; + @Before + public void cleanUpLogs() { + FileSystemUtils.deleteRecursively(new File("target/transaction-logs")); + } + @After public void closeContext() { if (this.context != null) { @@ -94,6 +113,62 @@ public class JtaAutoConfigurationTests { this.context.getBean(JtaTransactionManager.class); } + @Test + public void defaultBitronixServerId() throws UnknownHostException { + this.context = new AnnotationConfigApplicationContext( + JtaPropertiesConfiguration.class, BitronixJtaConfiguration.class); + String serverId = this.context.getBean(bitronix.tm.Configuration.class) + .getServerId(); + assertThat(serverId, is(equalTo(InetAddress.getLocalHost().getHostAddress()))); + } + + @Test + public void customBitronixServerId() throws UnknownHostException { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jta.transactionManagerId:custom"); + this.context.register(JtaPropertiesConfiguration.class, + BitronixJtaConfiguration.class); + this.context.refresh(); + String serverId = this.context.getBean(bitronix.tm.Configuration.class) + .getServerId(); + assertThat(serverId, is(equalTo("custom"))); + } + + @Test + public void defaultAtomikosTransactionManagerName() throws UnknownHostException { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jta.logDir:target/transaction-logs"); + this.context.register(JtaPropertiesConfiguration.class, + AtomikosJtaConfiguration.class); + this.context.refresh(); + + File epochFile = new File("target/transaction-logs/" + + InetAddress.getLocalHost().getHostAddress() + ".tm0.epoch"); + assertTrue(epochFile.isFile()); + } + + @Test + public void customAtomikosTransactionManagerName() throws BeansException, Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jta.transactionManagerId:custom", + "spring.jta.logDir:target/transaction-logs"); + this.context.register(JtaPropertiesConfiguration.class, + AtomikosJtaConfiguration.class); + this.context.refresh(); + + File epochFile = new File("target/transaction-logs/custom0.epoch"); + assertTrue(epochFile.isFile()); + } + + @Configuration + @EnableConfigurationProperties(JtaProperties.class) + public static class JtaPropertiesConfiguration { + + } + @Configuration public static class CustomTransactionManagerConfig { 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 a3f5dd63373..436a3f97883 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2047,6 +2047,12 @@ customize the Atomikos `UserTransactionServiceIml`. See the {dc-spring-boot}/jta/atomikos/AtomikosProperties.{dc-ext}[`AtomikosProperties` javadoc] for complete details. +CAUTION: To ensure that multiple transaction managers can safely coordinate the same +resource managers, each Atomikos instance must be configured with a unique ID. By default +this ID is the IP address of the machine on which Atomikos is running. To ensure +uniqueness in production, you should configure the `spring.jta.transaction-manager-id` +property with a different value for each instance of your application. + === Using a Bitronix transaction manager @@ -2063,6 +2069,12 @@ are also bound to the `bitronix.tm.Configuration` bean, allowing for complete customization. See the http://btm.codehaus.org/api/2.0.1/bitronix/tm/Configuration.html[Bitronix documentation] for details. +CAUTION: To ensure that multiple transaction managers can safely coordinate the same +resource managers, each Bitronix instance must be configured with a unique ID. By default +this ID is the IP address of the machine on which Bitronix is running. To ensure +uniqueness in production, you should configure the `spring.jta.transaction-manager-id` +property with a different value for each instance of your application. + === Using a Java EE managed transaction manager