В основном вложенная транзакция была реализована с использованием EJB, теперь мы пытаемся реализовать вложенную транзакцию в POJO. Здесь мы использовали функцию ThreadLocal.
Понимание вложенной транзакции
Транзакции могут быть вложены одна в другую. Таким образом, внутренняя транзакция или внешняя транзакция может быть откатом или фиксацией без влияния на другую транзакцию.
Когда создается новая транзакция, она попадает под внешнюю транзакцию. Как только внутренняя транзакция завершена в случае фиксации или отката в случае, внешняя транзакция может выполнить фиксацию или откат, не связанные с внутренней транзакцией. Сначала закройте внутреннюю транзакцию и перейдите к внешней.
Реализация с использованием Simple POJO
Создание интерфейса, как показано ниже:
1
2
3
4
5
6
7
8
9
|
importjava.sql.Connection; publicinterfaceTransactionManager { Connection getConnection(); voidbeginTransaction(); void commit(); void rollback(); } |
Создание класса диспетчера транзакций, как показано ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
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; } } |
Здесь мы создали стек:
1
|
private Stack<Connection> connections = new Stack<Connection>(); |
Поскольку транзакции создаются как LIFO (Stack), мы использовали Stack из Java API для поддержки соединений для каждой транзакции:
1
|
public void beginTransaction() |
Начните транзакцию, чтобы начать новую транзакцию и добавить соединение в стек. AutoCommit был установлен в false:
1
|
public Connection getConnection() |
Получить соединение для текущих транзакций. Если он не существует, он создаст и добавит в стек:
1
|
public void commit() |
Зафиксируйте текущую транзакцию и закройте соединение, также удаленное из стека:
1
|
public void rollback() |
Откатить текущую транзакцию и закрыть соединение, также удаленное из стека.
Приведенный выше класс TransactionManagerStackImpl создаст вложенную транзакцию для одного потока.
Вложенная транзакция для многопоточности
В случае многопоточного приложения каждый поток имеет отдельную транзакцию и вложенную транзакцию.
Мы придумали использование ThreadLocal для управления стеком соединений.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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(); } } |
Здесь мы инициализируем TransactionManagerStackImpl для создания вложенной транзакции внутри потока.
тестирование
Для проверки вышеупомянутого выполните внутреннюю транзакцию и откатите внешнюю транзакцию.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
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(); } |
Результат
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
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--- |
название | emp_id |
---|---|
Ravi220 | 220 |
Ravi00 | 20 |
Ravi110 | 210 |
При откате внутренней транзакции и принятии внешней транзакции:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
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--- |
название | emp_id |
---|---|
Ravi00 | 10 |
Ravi220 | 120 |
Ravi110 | 110 |