傳統做法在 Service 方法中硬編碼事務控制:
public boolean buyBook(String bookId, int qty) {
Connection conn = ConnectionManager.getConnection();
try {
conn.setAutoCommit(false);
BookDAO dao = DAOFactory.getBookDAO();
if (dao.reduceInventory(conn, bookId, qty)) {
CustomerDAO cdao = DAOFactory.getCustomerDAO();
cdao.reduceAccount(conn, price);
// ... 其他業務
conn.commit();
}
} catch (SQLException e) {
conn.rollback();
throw new SystemException(e);
} finally {
conn.setAutoCommit(true);
}
}
三大弊端:
利用 JDK 動態代理(針對接口)將事務控制剝離:
public final class TransactionWrapper {
public static Object decorate(Object delegate) {
return Proxy.newProxyInstance(
delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(),
new TxInvocationHandler(delegate)
);
}
static class TxInvocationHandler implements InvocationHandler {
private final Object delegate;
TxInvocationHandler(Object delegate) { this.delegate = delegate; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Connection con = ConnectionManager.getConnection();
try {
con.setAutoCommit(false); // 開啟事務
Object result = method.invoke(delegate, args); // 調用業務方法
con.commit(); // 提交
con.setAutoCommit(true);
return result;
} catch (Throwable t) {
con.rollback(); // 回滾
con.setAutoCommit(true);
throw new SystemException(t);
}
}
}
}
public class BookStoreManagerImpl implements BookStoreManager {
public boolean buyBook(String bookId, int qty) throws SystemException {
Connection conn = ConnectionManager.getConnection();
BookDAO bookDAO = DAOFactory.getBookDAO();
CustomerDAO customerDAO = DAOFactory.getCustomerDAO();
if (bookDAO.reduceInventory(conn, bookId, qty)) {
BigDecimal price = bookDAO.getPrice(bookId);
return customerDAO.reduceAccount(conn, price.multiply(new BigDecimal(qty)));
}
return false;
}
}
public final class ManagerFactory {
// 帶事務代理版
public static BookStoreManager getBookStoreManagerTrans() {
return (BookStoreManager) TransactionWrapper.decorate(new BookStoreManagerImpl());
}
// 純業務版(用於嵌套調用,加入外層事務)
public static BookStoreManager getBookStoreManager() {
return new BookStoreManagerImpl();
}
}
硬編碼「所有方法開啟事務」不夠靈活。引入 XML 配置指定哪些方法需要事務:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="buy*" propagation="REQUIRED" rollback-for="SystemException"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
| 技術 | 原理 | 優點 | 局限 |
|---|---|---|---|
| JDK 動態代理 | 接口代理,反射調用 | 標準 API,無依賴 | 只能代理接口 |
| CGLIB | 字節碼生成子類 | 可代理類、無接口 | 不能代理 final 方法/類 |
| AspectJ (CTW/LTW) | 編譯期/類加載期織入 | 功能最全,無運行時開銷 | 構建複雜,學習曲線陡 |
Spring 默認:有接口用 JDK,無接口用 CGLIB。
REQUIRED(默認):有事務加入,無則新建REQUIRES_NEW:掛起當前,新建獨立事務NESTED:JDBC 3.0 Savepoint,子事務可獨立回滾SUPPORTS:有事務加入,無則非事務運行工廠雙模式本質是手動控制傳播:getBookStoreManagerTrans() = REQUIRED,getBookStoreManager() = SUPPORTS/MANDATORY。
從硬編碼 → 動態代理 → 聲明式 AOP,是事務管理架構演進的必經之路。核心思想:事務是橫切關注點,應與業務邏輯分離。Spring @Transactional 註解是這一思想的工業級實踐。理解底層原理(代理、攔截器鏈、傳播行為),能幫你在複雜場景(分布式事務、嵌套回滾、只讀優化)做出正確架構決策。