首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

利用動態代理與 AOP 實現資料庫交易管理

     編輯:Java 程式設計入門

核心問題:攔截器如何取得資料庫連線?

在進行資料庫操作時,DataSource 或 Connection 是不可或缺的。然而,DAO 層不應與應用伺服器耦合,因此通常直接使用 Connection。這引出一個關鍵問題:在 AOP 攔截器中如何獲取 Connection 物件?

本文探討兩種主流解決方案:

  1. 透過方法參數傳遞 Connection
  2. 利用 ThreadLocal 綁定連線

先決條件:統一異常處理

在討論具體實作前,先定義一個自訂執行期異常 TransactionException,封裝底層 SQLException,便於上層框架統一處理:

public class TransactionException extends RuntimeException {
    private Throwable superException;
    private String myMessage;

    public TransactionException(Throwable throwable) {
        super(throwable);
        this.superException = throwable;
    }

    public TransactionException(Throwable throwable, String message) {
        super(message, throwable);
        this.superException = throwable;
        this.myMessage = message;
    }

    // getter/setter 略...
}

方案一:將 Connection 作為方法第一個參數傳入

DAO 介面設計

所有需交易控制的方法,第一個參數皆為 Connection:

public class TestDao {
    public void insertA(Connection con, String a, String b) { /* ... */ }
    public String queryA(Connection con, ...) { /* ... */ }
    public void updateA(Connection con, ...) { /* ... */ }
}

交易攔截器實作

攔截器從 InvokeJniInfo 取得第一個參數,並在 before、after、exceptionThrow 三個切點管理交易生命週期:

public class TransactionInterceptor implements Interceptor {

    public void before(InvokeJniInfo invInfo) {
        if (isNeedTransactions(invInfo)) {
            Connection conn = (Connection) invInfo.getArgs()[0];
            try { conn.setAutoCommit(false); }
            catch (SQLException e) { throw new TransactionException(e); }
        }
    }

    public void after(InvokeJniInfo invInfo) {
        if (isNeedTransactions(invInfo)) {
            Connection conn = (Connection) invInfo.getArgs()[0];
            try {
                conn.commit();
            } catch (SQLException e) {
                throw new TransactionException(e);
            } finally {
                closeConnection(conn);
            }
        }
    }

    public void exceptionThrow(InvokeJniInfo invInfo) {
        if (isNeedTransactions(invInfo)) {
            Connection conn = (Connection) invInfo.getArgs()[0];
            try { conn.rollback(); }
            catch (SQLException e) { throw new TransactionException(e); }
            finally { closeConnection(conn); }
        }
    }

    private void closeConnection(Connection conn) {
        if (conn != null) {
            try { conn.close(); }
            catch (SQLException e) { throw new TransactionException(e, "Close Connection failure!"); }
        }
    }

    private List getNeedTransaction() {
        List list = new ArrayList<>();
        list.add("insert");
        list.add("update");
        return list;
    }

    private boolean isNeedTransactions(InvokeJniInfo invInfo) {
        String methodName = invInfo.getMethod().getName();
        for (String prefix : getNeedTransaction()) {
            if (methodName.startsWith(prefix)) return true;
        }
        return false;
    }
}

優點:實作直觀,連線生命週期由呼叫端控制。
缺點:DAO 介面被 Connection 汙染,違反關注點分離。

方案二:ThreadLocal 綁定連線

連線工具類

ConnectionUtil 封裝 ThreadLocal,確保同一執行緒共用同一連線:

public final class ConnectionUtil {
    private static ThreadLocal connections = new ThreadLocal<>();

    public static Connection getConnection() {
        Connection conn = connections.get();
        if (conn == null) {
            conn = getRealConnection(); // 實際取得連線邏輯
            connections.set(conn);
        }
        return conn;
    }

    public static void releaseConnection(Connection conn) {
        connections.remove(); // 避免記憶體洩漏
    }

    private static Connection getRealConnection() {
        // 從 DataSource 取得連線
        return null;
    }
}

DAO 簡化

DAO 不再需要傳遞 Connection,直接從工具類取得:

public class TestDao {
    public void insertA(String a, String b) {
        Connection conn = getConnection();
        // 業務邏輯
    }
    // 其他方法類似...

    private Connection getConnection() {
        return ConnectionUtil.getConnection();
    }
}

攔截器調整

攔截器同樣透過 ConnectionUtil 取得連線,並在 finally 區塊釋放:

public class TransactionInterceptor implements Interceptor {
    // before/after/exceptionThrow 邏輯同方案一,改用 ConnectionUtil.getConnection()

    private Connection getConnection() { return ConnectionUtil.getConnection(); }
    private void releaseConnection(Connection conn) { ConnectionUtil.releaseConnection(conn); }
    // isNeedTransactions 邏輯不變
}

整合至 AOP 框架

最後在 InterceptorHandler 註冊攔截器:

private synchronized List getInterceptors() {
    if (interceptors == null) {
        interceptors = new ArrayList<>();
        interceptors.add(new TransactionInterceptor());
        // 其他攔截器...
    }
    return interceptors;
}

總結

  • 參數傳遞法 適合既有程式碼少、DAO 介面可變更的情境。
  • ThreadLocal 法 乾淨分離關注點,DAO 介面不受交易管理影響,較符合現代框架設計(如 Spring 的 TransactionSynchronizationManager)。
  • 無論採用哪種方式,核心在於:在 AOP 切點統一控制交易邊界(開啟、提交/回滾、關閉),避免業務程式碼散落交易邏輯。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved