I have need for two REST services that are part of the single transaction(distributed transaction), so if for example, client calls SERVICE_1 that is successful, and then SERVICE_2 that is not successful, SERVICE_2 AND SERVICE_1 must perform rollback.
I successfully achieved that with Atomikos transaction manager (TransactionEssentials), services implemented with Apache CXF, and Java client (transaction orchestrator) that calls REST services with JAX-RS. I based my work on great examples provided by Atomikos, that can be found here: https://www.atomikos.com/downloads/extreme-transactions/com/atomikos/examples/5.0.6/examples-5.0.6-project.zip (examples-jta-rest-jaxrs).
The problem that I am facing is that when I replace REST services implementation with Spring Boot, the global transaction doesn't work any more. I narrowed the problem down to Spring Boot REST service that doesn't return 'Atomikos-Extent' attribute in the header that is required by the caller(that begins and commits global transaction). This is the exception that caller throws:
WARNING: Invalid extent found - any remote work will time out and rollback.
java.lang.IllegalArgumentException: Expected an extent but found none. The remote work will not be committed by us.
at com.atomikos.remoting.DefaultExportingTransactionManager.addExtent(DefaultExportingTransactionManager.java:70)
So, the question is, how to implement Spring Boot REST service, so that it can participate in global transactions with Atomikos transaction manager.
Bellow are some code snippets that could be important.
Client/caller
Client client1= newClient().register(JacksonJsonProvider.class)
.register(ParticipantsProvider.class)
.register(TransactionAwareRestClientFilter.class);
Client client2 = //same as client1
UserTransactionManager utm = new UserTransactionManager();
utm.init();
utm.begin();
callService1();
callService2();
utm.commit();
utm.close();
Service
@EnableTransactionManagement
@SpringBootApplication
public class SpringBootAtomikosExampleApplication {
//..
@Bean(initMethod="init", destroyMethod="close")
public AtomikosDataSourceBean dataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
//..
return ds;
}
}
@Transactional
@RestController
public class MyController {
@RequestMapping("/test")
public void test() throws Exception {
TransactionManager tm = new UserTransactionManager();
tm.begin();
try (Connection con = dataSource.getConnection();
Statement s = con.createStatement();) {
s.executeQuery("select count(*) from menu");
}
tm.commit();
}
}
I found vague information that with spring boot you should use Atomikos ExtremeTransaction instead of TransactionEssentials, but I am not sure if that is the solution.