We have an application running in Liberty 19.X using Spring 5 and Spring Boot 2 which is live in production and working properly. We are migrating that application to Liberty 24.X with Spring 6 and Spring Boot 3. Unfortunately, Spring 6 dropped explicit support for WebSphere/Liberty's JTA transaction management. Spring 5 had a class named WebSphereUowTransactionManager specifically for handling this situation, but it was dropped in Spring 6 and we now have to use the generic JtaTransactionManager. We need a JTA transaction manager because we are connecting to both a database and a JMS queue.
I was able to get the JtaTranactionManager working by manually creating the JtaTransactionManager and setting the Transaction Manager using reflection. Liberty does not provide a JNDI lookup for the IBM transaction manager. Unfortunately, JtaTransactionManager does not support transaction isolation levels, which are required for Spring Integration to work properly (DefaultLockRepository sets an isolation level when attempting to acquire a lock).
NOTE: I see that JtaTransactionManager does provide a "allowCustomIsolationLevels" property, but looking at the source code it looks like it just enables a bypass of the isolation level check rather than adding support for isolation levels.
What is the proper method to get a Spring JtaTransactionManager in IBM Liberty that supports transaction isolation levels?
Here is the declaration for my current JtaTransactionManager:
@Bean
@ConditionalOnClass(name = "com.ibm.tx.jta.TransactionManagerFactory") /* only load on Liberty app servers */
JtaTransactionManager transactionManager() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// unfortunately Liberty does not provide a transaction manager lookup via JNDI, so we have to find it using reflection
// Hibernate uses similar reflection code to find the IBM TransactionManager in class WebSphereLibertyJtaPlatform
// in the future, hopefully Liberty will allow us to get the transaction manager using JNDI and we can clean this up
TransactionManager ibmTm = (TransactionManager) Class
.forName("com.ibm.tx.jta.TransactionManagerFactory")
.getDeclaredMethod("getTransactionManager")
.invoke(null);
JtaTransactionManager tm = new JtaTransactionManager();
tm.setUserTransactionName("java:comp/UserTransaction"); // default
tm.setTransactionManager(ibmTm);
return tm;
}
We can wrap a datasource using Spring's IsolationLevelDataSourceAdapter class, which will set the transaction isolation when each DB connection is obtained to match the isolation level of the transaction.
Additionally, IBM support has notified me that a change in Open Liberty has been made in version 25.0.0.1 to allow Spring to directly look up the TransactionManager from JNDI using java:comp/UserTransaction.
@Bean
public DataSource dataSource(DataSourceProperties dsProperties) {
// create the datasource as defined in the application.yml
DataSource datasource = dsProperties.initializeDataSourceBuilder().build();
// alternatively, use JNDI to get the datasource
// DataSource datasource = (DataSource) new JndiTemplate().lookup(dsProperties.getJndiName());
// wrap the datasource to support custom isolation levels
IsolationLevelDataSourceAdapter isolationLevelDataSourceAdapter = new IsolationLevelDataSourceAdapter();
isolationLevelDataSourceAdapter.setTargetDataSource(datasource);
return isolationLevelDataSourceAdapter;
}
@Bean
@ConditionalOnClass(name = "com.ibm.tx.jta.TransactionManagerFactory") // only load on Liberty app servers
public TransactionManagerCustomizer<JtaTransactionManager> transactionManagerCustomizer() {
return (jtaTm) -> {
// unfortunately Liberty does not provide a transaction manager lookup via JNDI, so we have to find it using reflection
// Hibernate uses similar reflection code to find the IBM TransactionManager in class WebSphereLibertyJtaPlatform
// in the future, hopefully Liberty will allow us to get the transaction manager using JNDI and we can clean this up
// UPDATE: according to IBM Support, Liberty v25.0.0.1 will add a JNDI lookup for java:comp/UserTransaction
try {
TransactionManager ibmTm = (TransactionManager) Class
.forName("com.ibm.tx.jta.TransactionManagerFactory")
.getDeclaredMethod("getTransactionManager")
.invoke(null);
jtaTm.setTransactionManager(ibmTm);
jtaTm.setAllowCustomIsolationLevels(true); // this works in conjunction with the IsolationLevelDataSourceAdapter
}
catch (Exception e) {
throw new RuntimeException("Unable to retrieve IBM Transaction Manager", e);
}
};
}