mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Remove support for Atomikos
Closes gh-28589
This commit is contained in:
parent
900085628a
commit
ef02cc9bff
@ -12,8 +12,6 @@ description = "Spring Boot AutoConfigure"
|
||||
dependencies {
|
||||
api(project(":spring-boot-project:spring-boot"))
|
||||
|
||||
optional("com.atomikos:transactions-jdbc")
|
||||
optional("com.atomikos:transactions-jta")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind")
|
||||
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
|
||||
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
|
||||
@ -94,6 +92,7 @@ dependencies {
|
||||
optional("org.aspectj:aspectjweaver")
|
||||
optional("org.eclipse.jetty:jetty-webapp") {
|
||||
exclude group: "javax.servlet", module: "javax.servlet-api"
|
||||
exclude(group: "org.eclipse.jetty", module: "jetty-jndi")
|
||||
}
|
||||
optional("org.eclipse.jetty:jetty-reactive-httpclient")
|
||||
optional("org.eclipse.jetty.websocket:javax-websocket-server-impl") {
|
||||
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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.transaction.jta;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.jms.Message;
|
||||
import javax.transaction.TransactionManager;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import com.atomikos.icatch.config.UserTransactionService;
|
||||
import com.atomikos.icatch.config.UserTransactionServiceImp;
|
||||
import com.atomikos.icatch.jta.UserTransactionManager;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jdbc.XADataSourceWrapper;
|
||||
import org.springframework.boot.jms.XAConnectionFactoryWrapper;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosDependsOnBeanFactoryPostProcessor;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosProperties;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosXAConnectionFactoryWrapper;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosXADataSourceWrapper;
|
||||
import org.springframework.boot.system.ApplicationHome;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* JTA Configuration for <A href="https://www.atomikos.com/">Atomikos</a>.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Kazuki Shimizu
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties({ AtomikosProperties.class, JtaProperties.class })
|
||||
@ConditionalOnClass({ JtaTransactionManager.class, UserTransactionManager.class })
|
||||
@ConditionalOnMissingBean(org.springframework.transaction.TransactionManager.class)
|
||||
class AtomikosJtaConfiguration {
|
||||
|
||||
@Bean(initMethod = "init", destroyMethod = "shutdownWait")
|
||||
@ConditionalOnMissingBean(UserTransactionService.class)
|
||||
UserTransactionServiceImp userTransactionService(AtomikosProperties atomikosProperties,
|
||||
JtaProperties jtaProperties) {
|
||||
Properties properties = new Properties();
|
||||
if (StringUtils.hasText(jtaProperties.getTransactionManagerId())) {
|
||||
properties.setProperty("com.atomikos.icatch.tm_unique_name", jtaProperties.getTransactionManagerId());
|
||||
}
|
||||
properties.setProperty("com.atomikos.icatch.log_base_dir", getLogBaseDir(jtaProperties));
|
||||
properties.putAll(atomikosProperties.asProperties());
|
||||
return new UserTransactionServiceImp(properties);
|
||||
}
|
||||
|
||||
private String getLogBaseDir(JtaProperties jtaProperties) {
|
||||
if (StringUtils.hasLength(jtaProperties.getLogDir())) {
|
||||
return jtaProperties.getLogDir();
|
||||
}
|
||||
File home = new ApplicationHome().getDir();
|
||||
return new File(home, "transaction-logs").getAbsolutePath();
|
||||
}
|
||||
|
||||
@Bean(initMethod = "init", destroyMethod = "close")
|
||||
@ConditionalOnMissingBean(TransactionManager.class)
|
||||
UserTransactionManager atomikosTransactionManager(UserTransactionService userTransactionService) throws Exception {
|
||||
UserTransactionManager manager = new UserTransactionManager();
|
||||
manager.setStartupTransactionService(false);
|
||||
manager.setForceShutdown(true);
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(XADataSourceWrapper.class)
|
||||
AtomikosXADataSourceWrapper xaDataSourceWrapper() {
|
||||
return new AtomikosXADataSourceWrapper();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
static AtomikosDependsOnBeanFactoryPostProcessor atomikosDependsOnBeanFactoryPostProcessor() {
|
||||
return new AtomikosDependsOnBeanFactoryPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager,
|
||||
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
|
||||
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, transactionManager);
|
||||
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(jtaTransactionManager));
|
||||
return jtaTransactionManager;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Message.class)
|
||||
static class AtomikosJtaJmsConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(XAConnectionFactoryWrapper.class)
|
||||
AtomikosXAConnectionFactoryWrapper xaConnectionFactoryWrapper() {
|
||||
return new AtomikosXAConnectionFactoryWrapper();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -40,7 +40,7 @@ import org.springframework.context.annotation.Import;
|
||||
@ConditionalOnProperty(prefix = "spring.jta", value = "enabled", matchIfMissing = true)
|
||||
@AutoConfigureBefore({ XADataSourceAutoConfiguration.class, ActiveMQAutoConfiguration.class,
|
||||
ArtemisAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
|
||||
@Import({ JndiJtaConfiguration.class, AtomikosJtaConfiguration.class })
|
||||
@Import(JndiJtaConfiguration.class)
|
||||
public class JtaAutoConfiguration {
|
||||
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.transaction.jta;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
|
||||
/**
|
||||
* External configuration properties for a {@link JtaTransactionManager} created by
|
||||
* Spring. All {@literal spring.jta.} properties are also applied to the appropriate
|
||||
* vendor specific configuration.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.jta", ignoreUnknownFields = true)
|
||||
public class JtaProperties {
|
||||
|
||||
/**
|
||||
* Transaction logs directory.
|
||||
*/
|
||||
private String logDir;
|
||||
|
||||
/**
|
||||
* Transaction manager unique identifier.
|
||||
*/
|
||||
private String transactionManagerId;
|
||||
|
||||
public void setLogDir(String logDir) {
|
||||
this.logDir = logDir;
|
||||
}
|
||||
|
||||
public String getLogDir() {
|
||||
return this.logDir;
|
||||
}
|
||||
|
||||
public String getTransactionManagerId() {
|
||||
return this.transactionManagerId;
|
||||
}
|
||||
|
||||
public void setTransactionManagerId(String transactionManagerId) {
|
||||
this.transactionManagerId = transactionManagerId;
|
||||
}
|
||||
|
||||
}
|
@ -75,6 +75,7 @@ import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
@ -214,7 +215,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
||||
|
||||
@Test
|
||||
void jtaDefaultPlatform() {
|
||||
contextRunner().withConfiguration(AutoConfigurations.of(JtaAutoConfiguration.class))
|
||||
contextRunner().withUserConfiguration(JtaTransactionManagerConfiguration.class)
|
||||
.run(assertJtaPlatform(SpringJtaPlatform.class));
|
||||
}
|
||||
|
||||
@ -298,7 +299,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
||||
|
||||
@Test
|
||||
void providerDisablesAutoCommitIsNotConfiguredWithJta() {
|
||||
contextRunner().withConfiguration(AutoConfigurations.of(JtaAutoConfiguration.class))
|
||||
contextRunner().withUserConfiguration(JtaTransactionManagerConfiguration.class)
|
||||
.withPropertyValues("spring.datasource.type:" + HikariDataSource.class.getName(),
|
||||
"spring.datasource.hikari.auto-commit:false")
|
||||
.run((context) -> {
|
||||
@ -677,4 +678,17 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class JtaTransactionManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
JtaTransactionManager jtaTransactionManager() {
|
||||
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
|
||||
jtaTransactionManager.setUserTransaction(mock(UserTransaction.class));
|
||||
jtaTransactionManager.setTransactionManager(mock(TransactionManager.class));
|
||||
return jtaTransactionManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,29 +16,16 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.transaction.jta;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.TemporaryQueue;
|
||||
import javax.jms.XAConnection;
|
||||
import javax.jms.XAConnectionFactory;
|
||||
import javax.jms.XASession;
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.sql.DataSource;
|
||||
import javax.sql.XADataSource;
|
||||
import javax.transaction.TransactionManager;
|
||||
import javax.transaction.UserTransaction;
|
||||
import javax.transaction.xa.XAResource;
|
||||
|
||||
import com.atomikos.icatch.config.UserTransactionService;
|
||||
import com.atomikos.icatch.jta.UserTransactionManager;
|
||||
import com.atomikos.jms.AtomikosConnectionFactoryBean;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
@ -49,21 +36,13 @@ import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
|
||||
import org.junit.jupiter.api.extension.ParameterContext;
|
||||
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
||||
import org.junit.jupiter.api.extension.ParameterResolver;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.osjava.sj.loader.JndiLoader;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.XADataSourceWrapper;
|
||||
import org.springframework.boot.jms.XAConnectionFactoryWrapper;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosDependsOnBeanFactoryPostProcessor;
|
||||
import org.springframework.boot.jta.atomikos.AtomikosProperties;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.testsupport.BuildOutput;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -72,7 +51,6 @@ import org.springframework.transaction.jta.UserTransactionAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@ -86,9 +64,6 @@ import static org.mockito.Mockito.mock;
|
||||
*/
|
||||
class JtaAutoConfigurationTests {
|
||||
|
||||
private final File atomikosLogs = new File(new BuildOutput(JtaAutoConfigurationTests.class).getRootLocation(),
|
||||
"atomikos-logs");
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@AfterEach
|
||||
@ -132,81 +107,14 @@ class JtaAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableJtaSupport() {
|
||||
@ExtendWith(JndiExtension.class)
|
||||
void disableJtaSupport(InitialContext initialContext) throws NamingException {
|
||||
new JndiEntry("java:comp/UserTransaction", UserTransaction.class).register(initialContext);
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of("spring.jta.enabled:false").applyTo(this.context);
|
||||
this.context.register(JtaAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeansOfType(JtaTransactionManager.class)).isEmpty();
|
||||
assertThat(this.context.getBeansOfType(XADataSourceWrapper.class)).isEmpty();
|
||||
assertThat(this.context.getBeansOfType(XAConnectionFactoryWrapper.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomikosSanityCheck() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of("spring.jta.log-dir:" + this.atomikosLogs).applyTo(this.context);
|
||||
this.context.register(JtaProperties.class, AtomikosJtaConfiguration.class);
|
||||
this.context.refresh();
|
||||
this.context.getBean(AtomikosProperties.class);
|
||||
this.context.getBean(UserTransactionService.class);
|
||||
this.context.getBean(UserTransactionManager.class);
|
||||
this.context.getBean(UserTransaction.class);
|
||||
this.context.getBean(XADataSourceWrapper.class);
|
||||
this.context.getBean(XAConnectionFactoryWrapper.class);
|
||||
this.context.getBean(AtomikosDependsOnBeanFactoryPostProcessor.class);
|
||||
this.context.getBean(JtaTransactionManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultAtomikosTransactionManagerName(@TempDir Path dir) {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
File logs = new File(dir.toFile(), "jta");
|
||||
TestPropertyValues.of("spring.jta.logDir:" + logs.getAbsolutePath()).applyTo(this.context);
|
||||
this.context.register(AtomikosJtaConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
File epochFile = new File(logs, "tmlog0.log");
|
||||
assertThat(epochFile.isFile()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomikosConnectionFactoryPoolConfiguration() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of("spring.jta.atomikos.connectionfactory.minPoolSize:5",
|
||||
"spring.jta.atomikos.connectionfactory.maxPoolSize:10", "spring.jta.log-dir:" + this.atomikosLogs)
|
||||
.applyTo(this.context);
|
||||
this.context.register(AtomikosJtaConfiguration.class, PoolConfiguration.class);
|
||||
this.context.refresh();
|
||||
AtomikosConnectionFactoryBean connectionFactory = this.context.getBean(AtomikosConnectionFactoryBean.class);
|
||||
assertThat(connectionFactory.getMinPoolSize()).isEqualTo(5);
|
||||
assertThat(connectionFactory.getMaxPoolSize()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomikosDataSourcePoolConfiguration() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of("spring.jta.atomikos.datasource.minPoolSize:5",
|
||||
"spring.jta.atomikos.datasource.maxPoolSize:10", "spring.jta.log-dir:" + this.atomikosLogs)
|
||||
.applyTo(this.context);
|
||||
this.context.register(AtomikosJtaConfiguration.class, PoolConfiguration.class);
|
||||
this.context.refresh();
|
||||
AtomikosDataSourceBean dataSource = this.context.getBean(AtomikosDataSourceBean.class);
|
||||
assertThat(dataSource.getMinPoolSize()).isEqualTo(5);
|
||||
assertThat(dataSource.getMaxPoolSize()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomikosCustomizeJtaTransactionManagerUsingProperties() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertyValues.of("spring.transaction.default-timeout:30",
|
||||
"spring.transaction.rollback-on-commit-failure:true", "spring.jta.log-dir:" + this.atomikosLogs)
|
||||
.applyTo(this.context);
|
||||
this.context.register(AtomikosJtaConfiguration.class, TransactionAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
JtaTransactionManager transactionManager = this.context.getBean(JtaTransactionManager.class);
|
||||
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30);
|
||||
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ -219,31 +127,6 @@ class JtaAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class PoolConfiguration {
|
||||
|
||||
@Bean
|
||||
ConnectionFactory pooledConnectionFactory(XAConnectionFactoryWrapper wrapper) throws Exception {
|
||||
XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
|
||||
XAConnection connection = mock(XAConnection.class);
|
||||
XASession session = mock(XASession.class);
|
||||
TemporaryQueue queue = mock(TemporaryQueue.class);
|
||||
XAResource resource = mock(XAResource.class);
|
||||
given(connectionFactory.createXAConnection()).willReturn(connection);
|
||||
given(connection.createXASession()).willReturn(session);
|
||||
given(session.createTemporaryQueue()).willReturn(queue);
|
||||
given(session.getXAResource()).willReturn(resource);
|
||||
return wrapper.wrapConnectionFactory(connectionFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataSource pooledDataSource(XADataSourceWrapper wrapper) throws Exception {
|
||||
XADataSource dataSource = mock(XADataSource.class);
|
||||
return wrapper.wrapDataSource(dataSource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class JndiEntry {
|
||||
|
||||
private final String name;
|
||||
|
@ -793,10 +793,9 @@ features.caching.provider.caffeine=io.caching.provider.caffeine
|
||||
features.caching.provider.simple=io.caching.provider.simple
|
||||
features.caching.provider.none=io.caching.provider.none
|
||||
features.jta=io.jta
|
||||
features.jta.atomikos=io.jta.atomikos
|
||||
features.jta.javaee=io.jta.javaee
|
||||
features.jta.javaee=io.jta.jakartaee
|
||||
features.jta.mixing-xa-and-non-xa-connections=io.jta.mixing-xa-and-non-xa-connections
|
||||
features.jta.supporting-alternative-embedded-transaction-manager=io.jta.supporting-alternative-embedded-transaction-manager
|
||||
features.jta.supporting-alternative-embedded-transaction-manager=io.jta.supporting-embedded-transaction-manager
|
||||
features.email=io.email
|
||||
features.quartz=io.quartz
|
||||
features.resttemplate=io.rest-client.resttemplate
|
||||
|
@ -1,7 +1,6 @@
|
||||
[[io.jta]]
|
||||
== Distributed Transactions with JTA
|
||||
Spring Boot supports distributed JTA transactions across multiple XA resources by using an https://www.atomikos.com/[Atomikos] embedded transaction manager.
|
||||
JTA transactions are also supported when deploying to a suitable Java EE Application Server.
|
||||
Spring Boot supports distributed JTA transactions across multiple XA resources by using a transaction manager retrieved from JNDI.
|
||||
|
||||
When a JTA environment is detected, Spring's `JtaTransactionManager` is used to manage transactions.
|
||||
Auto-configured JMS, DataSource, and JPA beans are upgraded to support XA transactions.
|
||||
@ -10,28 +9,11 @@ If you are within a JTA environment and still want to use local transactions, yo
|
||||
|
||||
|
||||
|
||||
[[io.jta.atomikos]]
|
||||
=== Using an Atomikos Transaction Manager
|
||||
https://www.atomikos.com/[Atomikos] is a popular open source transaction manager which can be embedded into your Spring Boot application.
|
||||
You can use the `spring-boot-starter-jta-atomikos` starter to pull in the appropriate Atomikos libraries.
|
||||
Spring Boot auto-configures Atomikos and ensures that appropriate `depends-on` settings are applied to your Spring beans for correct startup and shutdown ordering.
|
||||
|
||||
By default, Atomikos transaction logs are written to a `transaction-logs` directory in your application's home directory (the directory in which your application jar file resides).
|
||||
You can customize the location of this directory by setting a configprop:spring.jta.log-dir[] property in your `application.properties` file.
|
||||
Properties starting with `spring.jta.atomikos.properties` can also be used to customize the Atomikos `UserTransactionServiceImp`.
|
||||
See the {spring-boot-module-api}/jta/atomikos/AtomikosProperties.html[`AtomikosProperties` Javadoc] for complete details.
|
||||
|
||||
NOTE: 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 configprop:spring.jta.transaction-manager-id[] property with a different value for each instance of your application.
|
||||
|
||||
|
||||
|
||||
[[io.jta.javaee]]
|
||||
=== Using a Java EE Managed Transaction Manager
|
||||
If you package your Spring Boot application as a `war` or `ear` file and deploy it to a Java EE application server, you can use your application server's built-in transaction manager.
|
||||
[[io.jta.jakartaee]]
|
||||
=== Using a Jakarta EE Managed Transaction Manager
|
||||
If you package your Spring Boot application as a `war` or `ear` file and deploy it to a Jakarta EE application server, you can use your application server's built-in transaction manager.
|
||||
Spring Boot tries to auto-configure a transaction manager by looking at common JNDI locations (`java:comp/UserTransaction`, `java:comp/TransactionManager`, and so on).
|
||||
If you use a transaction service provided by your application server, you generally also want to ensure that all resources are managed by the server and exposed over JNDI.
|
||||
When using a transaction service provided by your application server, you generally also want to ensure that all resources are managed by the server and exposed over JNDI.
|
||||
Spring Boot tries to auto-configure JMS by looking for a `ConnectionFactory` at the JNDI path (`java:/JmsXA` or `java:/XAConnectionFactory`), and you can use the <<data#data.sql.datasource.jndi, configprop:spring.datasource.jndi-name[] property>> to configure your `DataSource`.
|
||||
|
||||
|
||||
@ -65,10 +47,8 @@ include::{docs-java}/io/jta/mixingxaandnonxaconnections/xa/MyBean.java[tag=*]
|
||||
|
||||
|
||||
|
||||
[[io.jta.supporting-alternative-embedded-transaction-manager]]
|
||||
=== Supporting an Alternative Embedded Transaction Manager
|
||||
The {spring-boot-module-code}/jms/XAConnectionFactoryWrapper.java[`XAConnectionFactoryWrapper`] and {spring-boot-module-code}/jdbc/XADataSourceWrapper.java[`XADataSourceWrapper`] interfaces can be used to support alternative embedded transaction managers.
|
||||
[[io.jta.supporting-embedded-transaction-manager]]
|
||||
=== Supporting an Embedded Transaction Manager
|
||||
The {spring-boot-module-code}/jms/XAConnectionFactoryWrapper.java[`XAConnectionFactoryWrapper`] and {spring-boot-module-code}/jdbc/XADataSourceWrapper.java[`XADataSourceWrapper`] interfaces can be used to support embedded transaction managers.
|
||||
The interfaces are responsible for wrapping `XAConnectionFactory` and `XADataSource` beans and exposing them as regular `ConnectionFactory` and `DataSource` beans, which transparently enroll in the distributed transaction.
|
||||
DataSource and JMS auto-configuration use JTA variants, provided you have a `JtaTransactionManager` bean and appropriate XA wrapper beans registered within your `ApplicationContext`.
|
||||
|
||||
The {spring-boot-module-code}/jta/atomikos/AtomikosXAConnectionFactoryWrapper.java[AtomikosXAConnectionFactoryWrapper] and {spring-boot-module-code}/jta/atomikos/AtomikosXADataSourceWrapper.java[AtomikosXADataSourceWrapper] provide good examples of how to write XA wrappers.
|
||||
|
@ -1,13 +0,0 @@
|
||||
plugins {
|
||||
id "org.springframework.boot.starter"
|
||||
}
|
||||
|
||||
description = "Starter for JTA transactions using Atomikos"
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
|
||||
api("com.atomikos:transactions-jms")
|
||||
api("com.atomikos:transactions-jta")
|
||||
api("com.atomikos:transactions-jdbc")
|
||||
api("jakarta.transaction:jakarta.transaction-api")
|
||||
}
|
@ -22,9 +22,6 @@ dependencies {
|
||||
api("org.springframework:spring-context")
|
||||
|
||||
optional("ch.qos.logback:logback-classic")
|
||||
optional("com.atomikos:transactions-jdbc")
|
||||
optional("com.atomikos:transactions-jms")
|
||||
optional("com.atomikos:transactions-jta")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind")
|
||||
optional("com.h2database:h2")
|
||||
optional("com.google.code.gson:gson")
|
||||
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Spring friendly version of {@link com.atomikos.jms.AtomikosConnectionFactoryBean}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@ConfigurationProperties(prefix = "spring.jta.atomikos.connectionfactory")
|
||||
public class AtomikosConnectionFactoryBean extends com.atomikos.jms.AtomikosConnectionFactoryBean
|
||||
implements BeanNameAware, InitializingBean, DisposableBean {
|
||||
|
||||
private String beanName;
|
||||
|
||||
@Override
|
||||
public void setBeanName(String name) {
|
||||
this.beanName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (!StringUtils.hasLength(getUniqueResourceName())) {
|
||||
setUniqueResourceName(this.beanName);
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
close();
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Spring friendly version of {@link com.atomikos.jdbc.AtomikosDataSourceBean}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@ConfigurationProperties(prefix = "spring.jta.atomikos.datasource")
|
||||
public class AtomikosDataSourceBean extends com.atomikos.jdbc.AtomikosDataSourceBean
|
||||
implements BeanNameAware, InitializingBean, DisposableBean {
|
||||
|
||||
private String beanName;
|
||||
|
||||
@Override
|
||||
public void setBeanName(String name) {
|
||||
this.beanName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (!StringUtils.hasLength(getUniqueResourceName())) {
|
||||
setUniqueResourceName(this.beanName);
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
close();
|
||||
}
|
||||
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.atomikos.icatch.jta.UserTransactionManager;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} to automatically setup the recommended
|
||||
* {@link BeanDefinition#setDependsOn(String[]) dependsOn} settings for
|
||||
* <a href="https://www.atomikos.com/Documentation/SpringIntegration">correct Atomikos
|
||||
* ordering</a>.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class AtomikosDependsOnBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
||||
|
||||
private static final String[] NO_BEANS = {};
|
||||
|
||||
private int order = Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
String[] transactionManagers = beanFactory.getBeanNamesForType(UserTransactionManager.class, true, false);
|
||||
for (String transactionManager : transactionManagers) {
|
||||
addTransactionManagerDependencies(beanFactory, transactionManager);
|
||||
}
|
||||
addMessageDrivenContainerDependencies(beanFactory, transactionManagers);
|
||||
}
|
||||
|
||||
private void addTransactionManagerDependencies(ConfigurableListableBeanFactory beanFactory,
|
||||
String transactionManager) {
|
||||
BeanDefinition bean = beanFactory.getBeanDefinition(transactionManager);
|
||||
Set<String> dependsOn = new LinkedHashSet<>(asList(bean.getDependsOn()));
|
||||
int initialSize = dependsOn.size();
|
||||
addDependencies(beanFactory, "javax.jms.ConnectionFactory", dependsOn);
|
||||
addDependencies(beanFactory, "javax.sql.DataSource", dependsOn);
|
||||
if (dependsOn.size() != initialSize) {
|
||||
bean.setDependsOn(StringUtils.toStringArray(dependsOn));
|
||||
}
|
||||
}
|
||||
|
||||
private void addMessageDrivenContainerDependencies(ConfigurableListableBeanFactory beanFactory,
|
||||
String[] transactionManagers) {
|
||||
String[] messageDrivenContainers = getBeanNamesForType(beanFactory,
|
||||
"com.atomikos.jms.extra.MessageDrivenContainer");
|
||||
for (String messageDrivenContainer : messageDrivenContainers) {
|
||||
BeanDefinition bean = beanFactory.getBeanDefinition(messageDrivenContainer);
|
||||
Set<String> dependsOn = new LinkedHashSet<>(asList(bean.getDependsOn()));
|
||||
dependsOn.addAll(asList(transactionManagers));
|
||||
bean.setDependsOn(StringUtils.toStringArray(dependsOn));
|
||||
}
|
||||
}
|
||||
|
||||
private void addDependencies(ConfigurableListableBeanFactory beanFactory, String type, Set<String> dependsOn) {
|
||||
dependsOn.addAll(asList(getBeanNamesForType(beanFactory, type)));
|
||||
}
|
||||
|
||||
private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, String type) {
|
||||
try {
|
||||
return beanFactory.getBeanNamesForType(Class.forName(type), true, false);
|
||||
}
|
||||
catch (ClassNotFoundException | NoClassDefFoundError ex) {
|
||||
// Ignore
|
||||
}
|
||||
return NO_BEANS;
|
||||
}
|
||||
|
||||
private List<String> asList(String[] array) {
|
||||
return (array != null) ? Arrays.asList(array) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
}
|
@ -1,425 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Bean friendly variant of
|
||||
* <a href="https://www.atomikos.com/Documentation/JtaProperties">Atomikos configuration
|
||||
* properties</a>. Allows for setter based configuration and is amiable to relaxed data
|
||||
* binding.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.2.0
|
||||
* @see #asProperties()
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.jta.atomikos.properties")
|
||||
public class AtomikosProperties {
|
||||
|
||||
/**
|
||||
* Transaction manager implementation that should be started.
|
||||
*/
|
||||
private String service;
|
||||
|
||||
/**
|
||||
* Maximum timeout that can be allowed for transactions.
|
||||
*/
|
||||
private Duration maxTimeout = Duration.ofMillis(300000);
|
||||
|
||||
/**
|
||||
* Default timeout for JTA transactions.
|
||||
*/
|
||||
private Duration defaultJtaTimeout = Duration.ofMillis(10000);
|
||||
|
||||
/**
|
||||
* Maximum number of active transactions.
|
||||
*/
|
||||
private int maxActives = 50;
|
||||
|
||||
/**
|
||||
* Whether to enable disk logging.
|
||||
*/
|
||||
private boolean enableLogging = true;
|
||||
|
||||
/**
|
||||
* The transaction manager's unique name. Defaults to the machine's IP address. If you
|
||||
* plan to run more than one transaction manager against one database you must set
|
||||
* this property to a unique value.
|
||||
*/
|
||||
private String transactionManagerUniqueName;
|
||||
|
||||
/**
|
||||
* Whether sub-transactions should be joined when possible.
|
||||
*/
|
||||
private boolean serialJtaTransactions = true;
|
||||
|
||||
/**
|
||||
* Specify whether sub-transactions are allowed.
|
||||
*/
|
||||
private boolean allowSubTransactions = true;
|
||||
|
||||
/**
|
||||
* Whether a VM shutdown should trigger forced shutdown of the transaction core.
|
||||
*/
|
||||
private boolean forceShutdownOnVmExit;
|
||||
|
||||
/**
|
||||
* How long should normal shutdown (no-force) wait for transactions to complete.
|
||||
*/
|
||||
private long defaultMaxWaitTimeOnShutdown = Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Transactions log file base name.
|
||||
*/
|
||||
private String logBaseName = "tmlog";
|
||||
|
||||
/**
|
||||
* Directory in which the log files should be stored. Defaults to the current working
|
||||
* directory.
|
||||
*/
|
||||
private String logBaseDir;
|
||||
|
||||
/**
|
||||
* Interval between checkpoints, expressed as the number of log writes between two
|
||||
* checkpoints. A checkpoint reduces the log file size at the expense of adding some
|
||||
* overhead in the runtime.
|
||||
*/
|
||||
private long checkpointInterval = 500;
|
||||
|
||||
/**
|
||||
* Whether to use different (and concurrent) threads for two-phase commit on the
|
||||
* participating resources.
|
||||
*/
|
||||
private boolean threadedTwoPhaseCommit;
|
||||
|
||||
private final Recovery recovery = new Recovery();
|
||||
|
||||
/**
|
||||
* Specifies the transaction manager implementation that should be started. There is
|
||||
* no default value and this must be set. Generally,
|
||||
* {@literal com.atomikos.icatch.standalone.UserTransactionServiceFactory} is the
|
||||
* value you should set.
|
||||
* @param service the service
|
||||
*/
|
||||
public void setService(String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum timeout that can be allowed for transactions. Defaults to
|
||||
* {@literal 300000}. This means that calls to UserTransaction.setTransactionTimeout()
|
||||
* with a value higher than configured here will be max'ed to this value.
|
||||
* @param maxTimeout the max timeout
|
||||
*/
|
||||
public void setMaxTimeout(Duration maxTimeout) {
|
||||
this.maxTimeout = maxTimeout;
|
||||
}
|
||||
|
||||
public Duration getMaxTimeout() {
|
||||
return this.maxTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default timeout for JTA transactions (optional, defaults to {@literal 10000}
|
||||
* ms).
|
||||
* @param defaultJtaTimeout the default JTA timeout
|
||||
*/
|
||||
public void setDefaultJtaTimeout(Duration defaultJtaTimeout) {
|
||||
this.defaultJtaTimeout = defaultJtaTimeout;
|
||||
}
|
||||
|
||||
public Duration getDefaultJtaTimeout() {
|
||||
return this.defaultJtaTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum number of active transactions. Defaults to {@literal 50}. A
|
||||
* negative value means infinite amount. You will get an {@code IllegalStateException}
|
||||
* with error message "Max number of active transactions reached" if you call
|
||||
* {@code UserTransaction.begin()} while there are already n concurrent transactions
|
||||
* running, n being this value.
|
||||
* @param maxActives the max activities
|
||||
*/
|
||||
public void setMaxActives(int maxActives) {
|
||||
this.maxActives = maxActives;
|
||||
}
|
||||
|
||||
public int getMaxActives() {
|
||||
return this.maxActives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if disk logging should be enabled or not. Defaults to true. It is useful
|
||||
* for JUnit testing, or to profile code without seeing the transaction manager's
|
||||
* activity as a hot spot but this should never be disabled on production or data
|
||||
* integrity cannot be guaranteed.
|
||||
* @param enableLogging if logging is enabled
|
||||
*/
|
||||
public void setEnableLogging(boolean enableLogging) {
|
||||
this.enableLogging = enableLogging;
|
||||
}
|
||||
|
||||
public boolean isEnableLogging() {
|
||||
return this.enableLogging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the transaction manager's unique name. Defaults to the machine's IP
|
||||
* address. If you plan to run more than one transaction manager against one database
|
||||
* you must set this property to a unique value or you might run into duplicate
|
||||
* transaction ID (XID) problems that can be quite subtle (example:
|
||||
* {@literal https://fogbugz.atomikos.com/default.asp?community.6.2225.7}). If
|
||||
* multiple instances need to use the same properties file then the easiest way to
|
||||
* ensure uniqueness for this property is by referencing a system property specified
|
||||
* at VM startup.
|
||||
* @param uniqueName the unique name
|
||||
*/
|
||||
public void setTransactionManagerUniqueName(String uniqueName) {
|
||||
this.transactionManagerUniqueName = uniqueName;
|
||||
}
|
||||
|
||||
public String getTransactionManagerUniqueName() {
|
||||
return this.transactionManagerUniqueName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if subtransactions should be joined when possible. Defaults to true. When
|
||||
* false, no attempt to call {@code XAResource.start(TM_JOIN)} will be made for
|
||||
* different but related subtransactions. This setting has no effect on resource
|
||||
* access within one and the same transaction. If you don't use subtransactions then
|
||||
* this setting can be ignored.
|
||||
* @param serialJtaTransactions if serial JTA transactions are supported
|
||||
*/
|
||||
public void setSerialJtaTransactions(boolean serialJtaTransactions) {
|
||||
this.serialJtaTransactions = serialJtaTransactions;
|
||||
}
|
||||
|
||||
public boolean isSerialJtaTransactions() {
|
||||
return this.serialJtaTransactions;
|
||||
}
|
||||
|
||||
public void setAllowSubTransactions(boolean allowSubTransactions) {
|
||||
this.allowSubTransactions = allowSubTransactions;
|
||||
}
|
||||
|
||||
public boolean isAllowSubTransactions() {
|
||||
return this.allowSubTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies whether VM shutdown should trigger forced shutdown of the transaction
|
||||
* core. Defaults to false.
|
||||
* @param forceShutdownOnVmExit if VM shutdown should be forced
|
||||
*/
|
||||
public void setForceShutdownOnVmExit(boolean forceShutdownOnVmExit) {
|
||||
this.forceShutdownOnVmExit = forceShutdownOnVmExit;
|
||||
}
|
||||
|
||||
public boolean isForceShutdownOnVmExit() {
|
||||
return this.forceShutdownOnVmExit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how long should a normal shutdown (no-force) wait for transactions to
|
||||
* complete. Defaults to {@literal Long.MAX_VALUE}.
|
||||
* @param defaultMaxWaitTimeOnShutdown the default max wait time on shutdown
|
||||
*/
|
||||
public void setDefaultMaxWaitTimeOnShutdown(long defaultMaxWaitTimeOnShutdown) {
|
||||
this.defaultMaxWaitTimeOnShutdown = defaultMaxWaitTimeOnShutdown;
|
||||
}
|
||||
|
||||
public long getDefaultMaxWaitTimeOnShutdown() {
|
||||
return this.defaultMaxWaitTimeOnShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the transactions log file base name. Defaults to {@literal tmlog}. The
|
||||
* transactions logs are stored in files using this name appended with a number and
|
||||
* the extension {@literal .log}. At checkpoint, a new transactions log file is
|
||||
* created and the number is incremented.
|
||||
* @param logBaseName the log base name
|
||||
*/
|
||||
public void setLogBaseName(String logBaseName) {
|
||||
this.logBaseName = logBaseName;
|
||||
}
|
||||
|
||||
public String getLogBaseName() {
|
||||
return this.logBaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the directory in which the log files should be stored. Defaults to the
|
||||
* current working directory. This directory should be a stable storage like a SAN,
|
||||
* RAID or at least backed up location. The transactions logs files are as important
|
||||
* as the data themselves to guarantee consistency in case of failures.
|
||||
* @param logBaseDir the log base dir
|
||||
*/
|
||||
public void setLogBaseDir(String logBaseDir) {
|
||||
this.logBaseDir = logBaseDir;
|
||||
}
|
||||
|
||||
public String getLogBaseDir() {
|
||||
return this.logBaseDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the interval between checkpoints. A checkpoint reduces the log file size
|
||||
* at the expense of adding some overhead in the runtime. Defaults to {@literal 500}.
|
||||
* @param checkpointInterval the checkpoint interval
|
||||
*/
|
||||
public void setCheckpointInterval(long checkpointInterval) {
|
||||
this.checkpointInterval = checkpointInterval;
|
||||
}
|
||||
|
||||
public long getCheckpointInterval() {
|
||||
return this.checkpointInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies whether or not to use different (and concurrent) threads for two-phase
|
||||
* commit on the participating resources. Setting this to {@literal true} implies that
|
||||
* the commit is more efficient since waiting for acknowledgements is done in
|
||||
* parallel. Defaults to {@literal true}. If you set this to {@literal false}, then
|
||||
* commits will happen in the order that resources are accessed within the
|
||||
* transaction.
|
||||
* @param threadedTwoPhaseCommit if threaded two phase commits should be used
|
||||
*/
|
||||
public void setThreadedTwoPhaseCommit(boolean threadedTwoPhaseCommit) {
|
||||
this.threadedTwoPhaseCommit = threadedTwoPhaseCommit;
|
||||
}
|
||||
|
||||
public boolean isThreadedTwoPhaseCommit() {
|
||||
return this.threadedTwoPhaseCommit;
|
||||
}
|
||||
|
||||
public Recovery getRecovery() {
|
||||
return this.recovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties as a {@link Properties} object that can be used with
|
||||
* Atomikos.
|
||||
* @return the properties
|
||||
*/
|
||||
public Properties asProperties() {
|
||||
Properties properties = new Properties();
|
||||
set(properties, "service", getService());
|
||||
set(properties, "max_timeout", getMaxTimeout());
|
||||
set(properties, "default_jta_timeout", getDefaultJtaTimeout());
|
||||
set(properties, "max_actives", getMaxActives());
|
||||
set(properties, "enable_logging", isEnableLogging());
|
||||
set(properties, "tm_unique_name", getTransactionManagerUniqueName());
|
||||
set(properties, "serial_jta_transactions", isSerialJtaTransactions());
|
||||
set(properties, "allow_subtransactions", isAllowSubTransactions());
|
||||
set(properties, "force_shutdown_on_vm_exit", isForceShutdownOnVmExit());
|
||||
set(properties, "default_max_wait_time_on_shutdown", getDefaultMaxWaitTimeOnShutdown());
|
||||
set(properties, "log_base_name", getLogBaseName());
|
||||
set(properties, "log_base_dir", getLogBaseDir());
|
||||
set(properties, "checkpoint_interval", getCheckpointInterval());
|
||||
set(properties, "threaded_2pc", isThreadedTwoPhaseCommit());
|
||||
Recovery recovery = getRecovery();
|
||||
set(properties, "forget_orphaned_log_entries_delay", recovery.getForgetOrphanedLogEntriesDelay());
|
||||
set(properties, "recovery_delay", recovery.getDelay());
|
||||
set(properties, "oltp_max_retries", recovery.getMaxRetries());
|
||||
set(properties, "oltp_retry_interval", recovery.getRetryInterval());
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void set(Properties properties, String key, Object value) {
|
||||
String id = "com.atomikos.icatch." + key;
|
||||
if (value != null && !properties.containsKey(id)) {
|
||||
properties.setProperty(id, asString(value));
|
||||
}
|
||||
}
|
||||
|
||||
private String asString(Object value) {
|
||||
if (value instanceof Duration) {
|
||||
return String.valueOf(((Duration) value).toMillis());
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recovery specific settings.
|
||||
*/
|
||||
public static class Recovery {
|
||||
|
||||
/**
|
||||
* Delay after which recovery can cleanup pending ('orphaned') log entries.
|
||||
*/
|
||||
private Duration forgetOrphanedLogEntriesDelay = Duration.ofMillis(86400000);
|
||||
|
||||
/**
|
||||
* Delay between two recovery scans.
|
||||
*/
|
||||
private Duration delay = Duration.ofMillis(10000);
|
||||
|
||||
/**
|
||||
* Number of retry attempts to commit the transaction before throwing an
|
||||
* exception.
|
||||
*/
|
||||
private int maxRetries = 5;
|
||||
|
||||
/**
|
||||
* Delay between retry attempts.
|
||||
*/
|
||||
private Duration retryInterval = Duration.ofMillis(10000);
|
||||
|
||||
public Duration getForgetOrphanedLogEntriesDelay() {
|
||||
return this.forgetOrphanedLogEntriesDelay;
|
||||
}
|
||||
|
||||
public void setForgetOrphanedLogEntriesDelay(Duration forgetOrphanedLogEntriesDelay) {
|
||||
this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay;
|
||||
}
|
||||
|
||||
public Duration getDelay() {
|
||||
return this.delay;
|
||||
}
|
||||
|
||||
public void setDelay(Duration delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public int getMaxRetries() {
|
||||
return this.maxRetries;
|
||||
}
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public Duration getRetryInterval() {
|
||||
return this.retryInterval;
|
||||
}
|
||||
|
||||
public void setRetryInterval(Duration retryInterval) {
|
||||
this.retryInterval = retryInterval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.XAConnectionFactory;
|
||||
|
||||
import org.springframework.boot.jms.XAConnectionFactoryWrapper;
|
||||
|
||||
/**
|
||||
* {@link XAConnectionFactoryWrapper} that uses an {@link AtomikosConnectionFactoryBean}
|
||||
* to wrap a {@link XAConnectionFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class AtomikosXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper {
|
||||
|
||||
@Override
|
||||
public ConnectionFactory wrapConnectionFactory(XAConnectionFactory connectionFactory) {
|
||||
AtomikosConnectionFactoryBean bean = new AtomikosConnectionFactoryBean();
|
||||
bean.setXaConnectionFactory(connectionFactory);
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import javax.sql.XADataSource;
|
||||
|
||||
import org.springframework.boot.jdbc.XADataSourceWrapper;
|
||||
|
||||
/**
|
||||
* {@link XADataSourceWrapper} that uses an {@link AtomikosDataSourceBean} to wrap a
|
||||
* {@link XADataSource}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class AtomikosXADataSourceWrapper implements XADataSourceWrapper {
|
||||
|
||||
@Override
|
||||
public AtomikosDataSourceBean wrapDataSource(XADataSource dataSource) throws Exception {
|
||||
AtomikosDataSourceBean bean = new AtomikosDataSourceBean();
|
||||
bean.setXaDataSource(dataSource);
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support classes for Atomikos JTA.
|
||||
*/
|
||||
package org.springframework.boot.jta.atomikos;
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for the Java Transaction API.
|
||||
*/
|
||||
package org.springframework.boot.jta;
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link AtomikosConnectionFactoryBean}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AtomikosConnectionFactoryBeanTests {
|
||||
|
||||
@Test
|
||||
void beanMethods() throws Exception {
|
||||
MockAtomikosConnectionFactoryBean bean = spy(new MockAtomikosConnectionFactoryBean());
|
||||
bean.setBeanName("bean");
|
||||
bean.afterPropertiesSet();
|
||||
assertThat(bean.getUniqueResourceName()).isEqualTo("bean");
|
||||
verify(bean).init();
|
||||
verify(bean, never()).close();
|
||||
bean.destroy();
|
||||
verify(bean).close();
|
||||
}
|
||||
|
||||
static class MockAtomikosConnectionFactoryBean extends AtomikosConnectionFactoryBean {
|
||||
|
||||
@Override
|
||||
public synchronized void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link AtomikosDataSourceBean}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AtomikosDataSourceBeanTests {
|
||||
|
||||
@Test
|
||||
void beanMethods() throws Exception {
|
||||
MockAtomikosDataSourceBean bean = spy(new MockAtomikosDataSourceBean());
|
||||
bean.setBeanName("bean");
|
||||
bean.afterPropertiesSet();
|
||||
assertThat(bean.getUniqueResourceName()).isEqualTo("bean");
|
||||
verify(bean).init();
|
||||
verify(bean, never()).close();
|
||||
bean.destroy();
|
||||
verify(bean).close();
|
||||
}
|
||||
|
||||
static class MockAtomikosDataSourceBean extends AtomikosDataSourceBean {
|
||||
|
||||
@Override
|
||||
public synchronized void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.atomikos.icatch.jta.UserTransactionManager;
|
||||
import com.atomikos.jms.extra.MessageDrivenContainer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AtomikosDependsOnBeanFactoryPostProcessor}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AtomikosDependsOnBeanFactoryPostProcessorTests {
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@Test
|
||||
void setsDependsOn() {
|
||||
this.context = new AnnotationConfigApplicationContext(Config.class);
|
||||
assertDependsOn("dataSource");
|
||||
assertDependsOn("connectionFactory");
|
||||
assertDependsOn("userTransactionManager", "dataSource", "connectionFactory");
|
||||
assertDependsOn("messageDrivenContainer", "userTransactionManager");
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
private void assertDependsOn(String bean, String... expected) {
|
||||
BeanDefinition definition = this.context.getBeanDefinition(bean);
|
||||
if (definition.getDependsOn() == null) {
|
||||
assertThat(expected).as("No dependsOn expected for " + bean).isEmpty();
|
||||
return;
|
||||
}
|
||||
HashSet<String> dependsOn = new HashSet<>(Arrays.asList(definition.getDependsOn()));
|
||||
assertThat(dependsOn).isEqualTo(new HashSet<>(Arrays.asList(expected)));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ConnectionFactory connectionFactory() {
|
||||
return mock(ConnectionFactory.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserTransactionManager userTransactionManager() {
|
||||
return mock(UserTransactionManager.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageDrivenContainer messageDrivenContainer() {
|
||||
return mock(MessageDrivenContainer.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
static AtomikosDependsOnBeanFactoryPostProcessor atomikosPostProcessor() {
|
||||
return new AtomikosDependsOnBeanFactoryPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.assertj.core.data.MapEntry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Tests for {@link AtomikosProperties}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class AtomikosPropertiesTests {
|
||||
|
||||
private AtomikosProperties properties = new AtomikosProperties();
|
||||
|
||||
@Test
|
||||
void testProperties() {
|
||||
this.properties.setService("service");
|
||||
this.properties.setMaxTimeout(Duration.ofMillis(1));
|
||||
this.properties.setDefaultJtaTimeout(Duration.ofMillis(2));
|
||||
this.properties.setMaxActives(3);
|
||||
this.properties.setEnableLogging(true);
|
||||
this.properties.setTransactionManagerUniqueName("uniqueName");
|
||||
this.properties.setSerialJtaTransactions(true);
|
||||
this.properties.setAllowSubTransactions(false);
|
||||
this.properties.setForceShutdownOnVmExit(true);
|
||||
this.properties.setDefaultMaxWaitTimeOnShutdown(20);
|
||||
this.properties.setLogBaseName("logBaseName");
|
||||
this.properties.setLogBaseDir("logBaseDir");
|
||||
this.properties.setCheckpointInterval(4);
|
||||
this.properties.setThreadedTwoPhaseCommit(true);
|
||||
this.properties.getRecovery().setForgetOrphanedLogEntriesDelay(Duration.ofMillis(2000));
|
||||
this.properties.getRecovery().setDelay(Duration.ofMillis(3000));
|
||||
this.properties.getRecovery().setMaxRetries(10);
|
||||
this.properties.getRecovery().setRetryInterval(Duration.ofMillis(4000));
|
||||
assertThat(this.properties.asProperties().size()).isEqualTo(18);
|
||||
assertProperty("com.atomikos.icatch.service", "service");
|
||||
assertProperty("com.atomikos.icatch.max_timeout", "1");
|
||||
assertProperty("com.atomikos.icatch.default_jta_timeout", "2");
|
||||
assertProperty("com.atomikos.icatch.max_actives", "3");
|
||||
assertProperty("com.atomikos.icatch.enable_logging", "true");
|
||||
assertProperty("com.atomikos.icatch.tm_unique_name", "uniqueName");
|
||||
assertProperty("com.atomikos.icatch.serial_jta_transactions", "true");
|
||||
assertProperty("com.atomikos.icatch.allow_subtransactions", "false");
|
||||
assertProperty("com.atomikos.icatch.force_shutdown_on_vm_exit", "true");
|
||||
assertProperty("com.atomikos.icatch.default_max_wait_time_on_shutdown", "20");
|
||||
assertProperty("com.atomikos.icatch.log_base_name", "logBaseName");
|
||||
assertProperty("com.atomikos.icatch.log_base_dir", "logBaseDir");
|
||||
assertProperty("com.atomikos.icatch.checkpoint_interval", "4");
|
||||
assertProperty("com.atomikos.icatch.threaded_2pc", "true");
|
||||
assertProperty("com.atomikos.icatch.forget_orphaned_log_entries_delay", "2000");
|
||||
assertProperty("com.atomikos.icatch.recovery_delay", "3000");
|
||||
assertProperty("com.atomikos.icatch.oltp_max_retries", "10");
|
||||
assertProperty("com.atomikos.icatch.oltp_retry_interval", "4000");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultProperties() {
|
||||
Properties defaultSettings = loadDefaultSettings();
|
||||
Properties properties = this.properties.asProperties();
|
||||
assertThat(properties).contains(defaultOf(defaultSettings, "com.atomikos.icatch.max_timeout",
|
||||
"com.atomikos.icatch.default_jta_timeout", "com.atomikos.icatch.max_actives",
|
||||
"com.atomikos.icatch.enable_logging", "com.atomikos.icatch.serial_jta_transactions",
|
||||
"com.atomikos.icatch.allow_subtransactions", "com.atomikos.icatch.force_shutdown_on_vm_exit",
|
||||
"com.atomikos.icatch.default_max_wait_time_on_shutdown", "com.atomikos.icatch.log_base_name",
|
||||
"com.atomikos.icatch.checkpoint_interval", "com.atomikos.icatch.threaded_2pc",
|
||||
"com.atomikos.icatch.forget_orphaned_log_entries_delay", "com.atomikos.icatch.oltp_max_retries",
|
||||
"com.atomikos.icatch.oltp_retry_interval"));
|
||||
assertThat(properties).contains(entry("com.atomikos.icatch.recovery_delay",
|
||||
defaultSettings.get("com.atomikos.icatch.default_jta_timeout")));
|
||||
assertThat(properties).hasSize(15);
|
||||
}
|
||||
|
||||
private MapEntry<?, ?>[] defaultOf(Properties defaultSettings, String... keys) {
|
||||
MapEntry<?, ?>[] entries = new MapEntry<?, ?>[keys.length];
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
String key = keys[i];
|
||||
entries[i] = entry(key, defaultSettings.get(key));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private Properties loadDefaultSettings() {
|
||||
try {
|
||||
|
||||
return PropertiesLoaderUtils.loadProperties(new ClassPathResource("transactions-defaults.properties"));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to get default from Atomikos", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertProperty(String key, String value) {
|
||||
assertThat(this.properties.asProperties().getProperty(key)).isEqualTo(value);
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.XAConnectionFactory;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AtomikosXAConnectionFactoryWrapper}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AtomikosXAConnectionFactoryWrapperTests {
|
||||
|
||||
@Test
|
||||
void wrap() {
|
||||
XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
|
||||
AtomikosXAConnectionFactoryWrapper wrapper = new AtomikosXAConnectionFactoryWrapper();
|
||||
ConnectionFactory wrapped = wrapper.wrapConnectionFactory(connectionFactory);
|
||||
assertThat(wrapped).isInstanceOf(AtomikosConnectionFactoryBean.class);
|
||||
assertThat(((AtomikosConnectionFactoryBean) wrapped).getXaConnectionFactory()).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jta.atomikos;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import javax.sql.XADataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AtomikosXADataSourceWrapper}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AtomikosXADataSourceWrapperTests {
|
||||
|
||||
@Test
|
||||
void wrap() throws Exception {
|
||||
XADataSource dataSource = mock(XADataSource.class);
|
||||
AtomikosXADataSourceWrapper wrapper = new AtomikosXADataSourceWrapper();
|
||||
DataSource wrapped = wrapper.wrapDataSource(dataSource);
|
||||
assertThat(wrapped).isInstanceOf(AtomikosDataSourceBean.class);
|
||||
assertThat(((AtomikosDataSourceBean) wrapped).getXaDataSource()).isSameAs(dataSource);
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot.conventions"
|
||||
}
|
||||
|
||||
description = "Spring Boot Atomikos JTA smoke test"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-artemis"))
|
||||
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-jpa"))
|
||||
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jta-atomikos"))
|
||||
implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||
if (JavaVersion.current().java9Compatible) {
|
||||
implementation("jakarta.xml.bind:jakarta.xml.bind-api")
|
||||
}
|
||||
implementation("org.springframework:spring-jms")
|
||||
|
||||
runtimeOnly("com.h2database:h2")
|
||||
runtimeOnly("org.apache.activemq:artemis-jms-server") {
|
||||
exclude group: "commons-logging", module: "commons-logging"
|
||||
exclude group: "org.apache.geronimo.specs", module: "geronimo-jms_2.0_spec"
|
||||
exclude group: "org.apache.geronimo.specs", module: "geronimo-json_1.0_spec"
|
||||
exclude group: "org.apache.geronimo.specs", module: "geronimo-jta_1.1_spec"
|
||||
}
|
||||
|
||||
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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 smoketest.atomikos;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class Account {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
Account() {
|
||||
}
|
||||
|
||||
public Account(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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 smoketest.atomikos;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface AccountRepository extends JpaRepository<Account, Long> {
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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 smoketest.atomikos;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class AccountService {
|
||||
|
||||
private final JmsTemplate jmsTemplate;
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
|
||||
public AccountService(JmsTemplate jmsTemplate, AccountRepository accountRepository) {
|
||||
this.jmsTemplate = jmsTemplate;
|
||||
this.accountRepository = accountRepository;
|
||||
}
|
||||
|
||||
public void createAccountAndNotify(String username) {
|
||||
this.jmsTemplate.convertAndSend("accounts", username);
|
||||
this.accountRepository.save(new Account(username));
|
||||
if ("error".equals(username)) {
|
||||
throw new RuntimeException("Simulated error");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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 smoketest.atomikos;
|
||||
|
||||
import org.springframework.jms.annotation.JmsListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class Messages {
|
||||
|
||||
@JmsListener(destination = "accounts")
|
||||
public void onMessage(String content) {
|
||||
System.out.println("----> " + content);
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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 smoketest.atomikos;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleAtomikosApplication {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(SampleAtomikosApplication.class, args)) {
|
||||
AccountService service = context.getBean(AccountService.class);
|
||||
AccountRepository repository = context.getBean(AccountRepository.class);
|
||||
service.createAccountAndNotify("josh");
|
||||
System.out.println("Count is " + repository.count());
|
||||
try {
|
||||
service.createAccountAndNotify("error");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
System.out.println(ex.getMessage());
|
||||
}
|
||||
System.out.println("Count is " + repository.count());
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
logging.level.com.atomikos=WARN
|
||||
spring.artemis.embedded.queues=accounts
|
||||
spring.jpa.open-in-view=true
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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 smoketest.atomikos;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.boot.testsupport.BuildOutput;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Basic integration tests for demo application.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SampleAtomikosApplicationTests {
|
||||
|
||||
@Test
|
||||
void testTransactionRollback(CapturedOutput output) throws Exception {
|
||||
File logDir = new File(new BuildOutput(getClass()).getRootLocation(), "atomikos-logs");
|
||||
SampleAtomikosApplication.main(new String[] { "--spring.jta.log-dir=" + logDir });
|
||||
assertThat(output).satisfies(numberOfOccurrences("---->", 1));
|
||||
assertThat(output).satisfies(numberOfOccurrences("----> josh", 1));
|
||||
assertThat(output).satisfies(numberOfOccurrences("Count is 1", 2));
|
||||
assertThat(output).satisfies(numberOfOccurrences("Simulated error", 1));
|
||||
}
|
||||
|
||||
private <T extends CharSequence> Consumer<T> numberOfOccurrences(String substring, int expectedCount) {
|
||||
return (charSequence) -> {
|
||||
int count = StringUtils.countOccurrencesOf(charSequence.toString(), substring);
|
||||
assertThat(count).isEqualTo(expectedCount);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user