In this tutorial we will learn how to use JBoss Transaction Manager on the top of Spring’s transaction management abstractions.
The Spring framework allows pluggable transaction managers. However, iIf you need XA support for your Spring applications, then you need to plug in a transaction manager such as JBossTS.
Let’s see how to configure JBoss Transaction Manager in a sample application which executes Transaction Statements using Spring JDBCTemplate and the default JBoss/WildFly DataSource for H2 database.
Here is the simple Customer class which works as a model:
package com.sample; public class Customer { private String name; private String surname; private int age; public Customer() { } public Customer(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Customer{" + "name='" + name + '\'' + ", surname='" + surname + '\'' + ", age=" + age + '}'; } }
We will use a DAO interface to handle basic SQL Operations on the model:
package com.sample; import java.util.List; public interface JDBCCustomerDAO { public void insert(Customer customer); public List<Customer> findAll(); public void createTable(); public void dropTable(); }
Here is the DAO implementation, which contains a method tagger as @Transactional with the attribute REQUIRED:
package com.sample; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class JDBCCustomerDAOImpl implements JDBCCustomerDAO { private DataSource dataSource; private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void createTable() { jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate .execute("create table customer (name varchar, surname varchar, age integer)"); System.out.println("Table created!"); } public void dropTable() { jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.execute("drop table customer"); System.out.println("Table dropped!"); } @Transactional(propagation = Propagation.REQUIRED) public void insert(Customer customer) { String sql = "INSERT INTO CUSTOMER " + "(NAME,SURNAME, AGE) VALUES (?, ?, ?)"; jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update(sql, new Object[] { customer.getName(), customer.getSurname(), customer.getAge() }); } @SuppressWarnings("rawtypes") public List<Customer> findAll() { jdbcTemplate = new JdbcTemplate(dataSource); String sql = "SELECT * FROM CUSTOMER"; List<Customer> customers = new ArrayList<Customer>(); List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql); for (Map row : rows) { Customer customer = new Customer(); customer.setSurname((String) row.get("SURNAME")); customer.setName((String) row.get("NAME")); customer.setAge(Integer.parseInt(String.valueOf(row.get("AGE")))); customers.add(customer); } return customers; } }
As soon as the Spring application is deployed, we will trigger the SQL Statements. For this purpose, we can deploy a WebListener which is fired when the application is deployed and undeployed:
package com.sample; import javax.inject.Inject; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import org.springframework.context.ConfigurableApplicationContext; import java.util.List; @WebListener public class MyContextListener implements ServletContextListener { @Inject ConfigurableApplicationContext context; @Override public void contextInitialized(ServletContextEvent sce) { JDBCCustomerDAO dao = (JDBCCustomerDAO) context .getBean("jdbcCustomerDAO"); dao.createTable(); Customer c = new Customer(); c.setAge(45); c.setName("ABC"); c.setSurname("DEF"); dao.insert(c); List<Customer> list = dao.findAll(); for (Customer customer:list) System.out.println(customer); } @Override public void contextDestroyed(ServletContextEvent sce) { JDBCCustomerDAO dao = (JDBCCustomerDAO) context .getBean("jdbcCustomerDAO"); dao.dropTable(); } }
In order to read Spring’s XML configuration, we can deploy an @ApplicationScoped CDI Bean which produces Spring’s ClassPathXmlApplicationContext from the applicationContext.xml:
package com.sample; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @ApplicationScoped public class SpringProducer { @Produces public ConfigurableApplicationContext create() { return new ClassPathXmlApplicationContext("applicationContext.xml"); } public void close(@Disposes final ConfigurableApplicationContext ctx) { ctx.close(); } }
Lastly, we will add a RowMapper for our Customer Class:
package com.sample; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.jdbc.core.RowMapper; @SuppressWarnings("rawtypes") public class CustomerRowMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Customer customer = new Customer(); customer.setSurname(rs.getString("SURNAME")); customer.setName(rs.getString("NAME")); customer.setAge(rs.getInt("AGE")); return customer; } }
We are done with the code. Let’s add the applicationContext.xml file in the resources folder of your Maven project:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd"> <context:component-scan base-package="com.sample" /> <bean id="jdbcCustomerDAO" class="com.sample.JDBCCustomerDAOImpl"> <property name="dataSource" ref="dataSource" /> </bean> <jee:jndi-lookup id="dataSource" jndi-name="java:jboss/datasources/ExampleDS"/> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:jboss/TransactionManager" /> <property name="userTransactionName" value="java:jboss/UserTransaction" /> </bean> <tx:annotation-driven transaction-manager="txManager" /> <tx:jta-transaction-manager /> </beans>
Please notice we are using as TransactionManager the JBoss TransactionManager which is looked up via JNDI. Also, we lookup the DataSource “java:jboss/datasources/ExampleDS” from the application server.
When we deploy the application, provided that we have set to TRACE the “com.arjuna.ats.jta”, we can see the Transaction enlist and delist of the transactional resource:
2020-09-30 14:25:46,563 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.equals 2020-09-30 14:25:46,564 DEBUG [org.springframework.jdbc.core.JdbcTemplate] (ServerService Thread Pool -- 92) Executing prepared SQL update 2020-09-30 14:25:46,564 DEBUG [org.springframework.jdbc.core.JdbcTemplate] (ServerService Thread Pool -- 92) Executing prepared SQL statement [INSERT INTO CUSTOMER (NAME,SURNAME, AGE) VALUES (?, ?, ?)] 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.equals 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.equals 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.equals 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.enlistResource ( LocalXAResourceImpl@45d3cf25[connectionListener=2f98cca3 connectionManager=74240d7a warned=false currentXid=null productName=H2 productVersion=@PROJECT_VERSION@ (2016-10-31) jndiName=java:jboss/datasources/ExampleDS] ) 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.arjuna] (ServerService Thread Pool -- 92) StateManager::StateManager( 0:0:0:0:1 ) 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.arjuna] (ServerService Thread Pool -- 92) AbstractRecord::AbstractRecord (0:0:0:0:1) 2020-09-30 14:25:46,564 TRACE [com.arjuna.ats.arjuna] (ServerService Thread Pool -- 92) LastResourceRecord() 2020-09-30 14:25:46,565 TRACE [com.arjuna.ats.arjuna] (ServerService Thread Pool -- 92) RecordList::insert(RecordList: empty) : appending /StateManager/AbstractRecord/LastResourceRecord for 0:0:0:0:1 2020-09-30 14:25:46,567 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,568 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,569 DEBUG [org.springframework.jdbc.core.JdbcTemplate] (ServerService Thread Pool -- 92) SQL update affected 1 rows 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) BaseTransaction.commit 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.commitAndDisassociate 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) SynchronizationImple.beforeCompletion - Class: class org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization HashCode: 1256027315 toString: org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization@4add74b3 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.equals 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) SynchronizationImple.beforeCompletion - Class: class org.wildfly.transaction.client.provider.jboss.JBossLocalTransactionProvider$1 HashCode: 706141901 toString: org.wildfly.transaction.client.provider.jboss.JBossLocalTransactionProvider$1@2a16decd 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) SynchronizationImple.beforeCompletion - Class: class org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization HashCode: 172725243 toString: org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization@a4b93fb 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.equals 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.getStatus: javax.transaction.Status.STATUS_ACTIVE 2020-09-30 14:25:46,569 TRACE [com.arjuna.ats.jta] (ServerService Thread Pool -- 92) TransactionImple.delistResource ( LocalXAResourceImpl@45d3cf25[connectionListener=2f98cca3 connectionManager=74240d7a warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffff0a057e34:-7a596d84:5f745755:27d, node_name=1, branch_uid=0:ffff0a057e34:-7a596d84:5f745755:281, subordinatenodename=null, eis_name=java:jboss/datasources/ExampleDS > productName=H2 productVersion=@PROJECT_VERSION@ (2016-10-31) jndiName=java:jboss/datasources/ExampleDS], 67108864 )
Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/spring/spring-demo-tx