Java Nested Transaction using ThreadLocal in POJO
Mostly nested transaction was implemented using EJB , now we try to implement the nested transaction on POJO. Here we have used the feature of ThreadLocal.
Understanding Nested Transaction
Transactions can be nested one inside another. So the inner transaction or outer transaction can be rollback or commit without affecting the other transaction.
When a new transaction is created then it comes under the outer transaction. Once the inner transaction is completed in either case commit or rollback, the outer transaction can perform either commit or rollback without related to inner transaction. First close the innermost transaction and move on to the outer.
Implementing using Simple POJO
Creating interface as below:
importjava.sql.Connection; publicinterfaceTransactionManager { Connection getConnection(); voidbeginTransaction(); void commit(); void rollback(); }
Creating Transaction Manager class as below:
importjava.sql.Connection; importjava.sql.DriverManager; importjava.sql.SQLException; importjava.util.Stack; publicclassTransactionManagerStackImplimplementsTransactionManager { private Stack<Connection>connections = new Stack<Connection>(); @Override public Connection getConnection() { if (connections.isEmpty()) { this.addConn(); } returnconnections.peek(); } @Override publicvoidbeginTransaction() { this.addConn(); } @Override publicvoid commit() { try { if (connections.peek() != null&& !connections.peek().isClosed()) { System.out.println(connections.peek().toString() +"--Commit---"); connections.peek().commit(); connections.pop().close(); } } catch (SQLException e) { e.printStackTrace(); } } @Override publicvoid rollback() { try { if (connections.peek() != null&& !connections.peek().isClosed()) { System.out.println(connections.peek().toString() +"--Rollback---"); connections.peek().rollback(); connections.pop().close(); } } catch (SQLException e) { e.printStackTrace(); } } privatevoidaddConn() { try { Connection con = this.getMysqlConnection(); con.setAutoCommit(false); connections.push(con); System.out.println(con.toString() +"--Conection---"); } catch (SQLException e) { e.printStackTrace(); } } private Connection getMysqlConnection() { returngetConnection("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/testdb", "test", "test12345"); } private Connection getConnection(String driver, String connection, String user, String password) { try { Class.forName(driver); returnDriverManager.getConnection(connection, user, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } returnnull; } }
Here we have created a Stack:
private Stack<Connection> connections = new Stack<Connection>();
As the transactions are created as LIFO (Stack) we have used Stack from Java API to maintain connections for each transaction:
public void beginTransaction()
Begin transaction to begin a new transaction and add the connection to the Stack. AutoCommit has been set to false:
public Connection getConnection()
Get Connection for the current transactions. If not exist it will create and add to stack:
public void commit()
Commit the current transaction and close the connection, also removed from stack:
public void rollback()
Rollback the current transaction and close the connection, also removed from stack.
The above class TransactionManagerStackImpl will create nested transaction for single thread.
Nested Transaction for Multithreads
In case of multithreaded application, each thread has separate transaction and nested transaction.
We came up using ThreadLocal to manage the stack of connections.
importjava.sql.Connection; publicclassTransactionManagerThreadLocalimplementsTransactionManager { privatestaticfinalThreadLocal<TransactionManager>tranManager = newThreadLocal<TransactionManager>() { protectedTransactionManagerinitialValue() { System.out.println(this.toString() + "--Thread Local Initialize--"); returnnewTransactionManagerStackImpl(); } }; @Override publicvoidbeginTransaction() { tranManager.get().beginTransaction(); } @Override publicvoid commit() { tranManager.get().commit(); } @Override publicvoid rollback() { tranManager.get().rollback(); } @Override public Connection getConnection() { returntranManager.get().getConnection(); } }
Here we initialize TransactionManagerStackImpl to create nested transaction inside the thread.
Testing
For testing this above, commit inner transaction and rollback outer transaction.
importjava.sql.Connection; publicclassNestedMainimplements Runnable { privateintv = 0; private String name; NestedMain(int v, String name) { this.v = v; this.name = name; } publicstaticvoid main(String[] args) throws Exception{ for (inti = 0; i< 3; i++) { NestedMain main = newNestedMain(i * 10, "Ravi" + i); new Thread(main).start(); } } @Override publicvoid run() { try { TransactionManagerThreadLocal local = newTransactionManagerThreadLocal(); // Transaction 1 ( outer ) local.beginTransaction(); Connection con = local.getConnection(); String sql = "INSERT INTO test_tran (emp_id, name) VALUES ('1"+v+"', '"+ name+v+"')"; this.insert(con, sql); // Transaction 2 ( Inner ) local.beginTransaction(); con = local.getConnection(); sql = "INSERT INTO test_tran (emp_id, name) VALUES ('2"+v+"', '"+ name+v+"')"; this.insert(con, sql); local.commit(); // Committing 2 local.rollback(); // Rollback 1 Outer } catch (Exception e) { e.printStackTrace(); }
Result
com.ttit.TransactionManagerThreadLocal$1@1270b73--Thread Local Initialize-- com.ttit.TransactionManagerThreadLocal$1@1270b73--Thread Local Initialize-- com.ttit.TransactionManagerThreadLocal$1@1270b73--Thread Local Initialize-- com.mysql.jdbc.JDBC4Connection@10dd1f7--Conection--- com.mysql.jdbc.JDBC4Connection@1813fac--Conection--- com.mysql.jdbc.JDBC4Connection@136228--Conection--- com.mysql.jdbc.JDBC4Connection@1855af5--Conection--- com.mysql.jdbc.JDBC4Connection@e39a3e--Conection--- com.mysql.jdbc.JDBC4Connection@1855af5--Commit--- com.mysql.jdbc.JDBC4Connection@e39a3e--Commit--- com.mysql.jdbc.JDBC4Connection@9fbe93--Conection--- com.mysql.jdbc.JDBC4Connection@9fbe93--Commit--- com.mysql.jdbc.JDBC4Connection@10dd1f7--Rollback--- com.mysql.jdbc.JDBC4Connection@1813fac--Rollback--- com.mysql.jdbc.JDBC4Connection@136228--Rollback---
name | emp_id |
---|---|
Ravi220 | 220 |
Ravi00 | 20 |
Ravi110 | 210 |
When rollback inner transaction and commit outer transaction:
com.ttit.TransactionManagerThreadLocal$1@1270b73--Thread Local Initialize-- com.ttit.TransactionManagerThreadLocal$1@1270b73--Thread Local Initialize-- com.ttit.TransactionManagerThreadLocal$1@1270b73--Thread Local Initialize-- com.mysql.jdbc.JDBC4Connection@9f2a0b--Conection--- com.mysql.jdbc.JDBC4Connection@136228--Conection--- com.mysql.jdbc.JDBC4Connection@1c672d0--Conection--- com.mysql.jdbc.JDBC4Connection@9fbe93--Conection--- com.mysql.jdbc.JDBC4Connection@1858610--Conection--- com.mysql.jdbc.JDBC4Connection@9fbe93--Rollback--- com.mysql.jdbc.JDBC4Connection@1858610--Rollback--- com.mysql.jdbc.JDBC4Connection@1a5ab41--Conection--- com.mysql.jdbc.JDBC4Connection@1a5ab41--Rollback--- com.mysql.jdbc.JDBC4Connection@9f2a0b--Commit--- com.mysql.jdbc.JDBC4Connection@136228--Commit--- com.mysql.jdbc.JDBC4Connection@1c672d0--Commit---
name | emp_id |
---|---|
Ravi00 | 10 |
Ravi220 | 120 |
Ravi110 | 110 |
Good to get a look at a basic nested transaction implementation. This gives a little peek into the JavaEE and Spring transactions.