Статьи

Вложенная транзакция Java с использованием ThreadLocal в POJO

В основном вложенная транзакция была реализована с использованием EJB, теперь мы пытаемся реализовать вложенную транзакцию в POJO. Здесь мы использовали функцию ThreadLocal.

Понимание вложенной транзакции

Транзакции могут быть вложены одна в другую. Таким образом, внутренняя транзакция или внешняя транзакция может быть откатом или фиксацией без влияния на другую транзакцию.

Когда создается новая транзакция, она попадает под внешнюю транзакцию. Как только внутренняя транзакция завершена в случае фиксации или отката в случае, внешняя транзакция может выполнить фиксацию или откат, не связанные с внутренней транзакцией. Сначала закройте внутреннюю транзакцию и перейдите к внешней.

Изображение1

Реализация с использованием 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

Ресурс: