在進行資料庫操作時,DataSource 或 Connection 是不可或缺的。然而,DAO 層不應與應用伺服器耦合,因此通常直接使用 Connection。這引出一個關鍵問題:在 AOP 攔截器中如何獲取 Connection 物件?
本文探討兩種主流解決方案:
ConnectionThreadLocal 綁定連線在討論具體實作前,先定義一個自訂執行期異常 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:
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 汙染,違反關注點分離。
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 不再需要傳遞 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 邏輯不變
}
最後在 InterceptorHandler 註冊攔截器:
private synchronized List getInterceptors() {
if (interceptors == null) {
interceptors = new ArrayList<>();
interceptors.add(new TransactionInterceptor());
// 其他攔截器...
}
return interceptors;
}
TransactionSynchronizationManager)。