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
This commit is contained in:
Andy Wilkinson 2014-10-16 09:33:44 +01:00
parent d251b51338
commit 01fd8cb8f3
5 changed files with 107 additions and 1 deletions

View File

@ -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);

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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