From 01fd8cb8f3ba906b8e022a06e1cef9339367ee73 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 16 Oct 2014 09:33:44 +0100 Subject: [PATCH] Reinstate Bitronix's default server ID, provide property to override it Previously, Bitronix's server ID was hard-coded to be spring-boot-jta-bitronix. This created the possibility of multiple transaction managers performing recovery on each other's behalf as they would be unable to identify their own XIDs due to the common server ID. This commit reinstates the default (which is the IP address of the machine on which Bitronix is running), and introduces a new property, spring.jta.transaction-manager-id, that can be used to configure the id for both Atomikos and Bitronix. A cautionary note has also been added to the documentation for Atomikos and Bitronix explaining the need to configure this property. Closes gh-1548 --- .../jta/AtomikosJtaConfiguration.java | 5 ++ .../jta/BitronixJtaConfiguration.java | 5 +- .../boot/autoconfigure/jta/JtaProperties.java | 11 +++ .../jta/JtaAutoConfigurationTests.java | 75 +++++++++++++++++++ .../main/asciidoc/spring-boot-features.adoc | 12 +++ 5 files changed, 107 insertions(+), 1 deletion(-) 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