在本系列的上一篇文章中,我們講到了使用Template模式進行事務管理,這固然是一種很好的方法,但是不那么完美的地方在於我們依然需要在service層中編寫和事務處理相關的代碼,即我們需要在service層中聲明一個TransactionTemplate。在本篇文章中,我們將使用Java提供的動態代理(Dynamic Proxy)功能來完成事務處理,你將看到無論是在service層還是DAO層都不會有事務處理代碼,即他們根本就意識不到事務處理的存在。使用動態代理完成事務處理也是AOP的一種典型應用。
這是一個關於Java事務處理的系列文章,請通過以下方式下載github源代碼:
git clone https://github.com/davenkin/java_transaction_workshop.git
Java動態代理的基本原理為:被代理對象需要實現某個接口(這是前提),代理對象會攔截對被代理對象的方法調用,在其中可以全然拋棄被代理對象的方法實現而完成另外的功能,也可以在被代理對象方法調用的前后增加一些額外的功能。在本篇文章中,我們將攔截service層的transfer方法,在其調用之前加入事務准備工作,然后調用原來的transfer方法,之后根據transfer方法是否執行成功決定commit還是rollback。
首先定義一個TransactionEnabledProxyManager類:
package davenkin.step5_transaction_proxy; import davenkin.step3_connection_holder.TransactionManager; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TransactionEnabledProxyManager { private TransactionManager transactionManager; public TransactionEnabledProxyManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public Object proxyFor(Object object) { return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new TransactionInvocationHandler(object, transactionManager)); } } class TransactionInvocationHandler implements InvocationHandler { private Object proxy; private TransactionManager transactionManager; TransactionInvocationHandler(Object object, TransactionManager transactionManager) { this.proxy = object; this.transactionManager = transactionManager; } public Object invoke(Object o, Method method, Object[] objects) throws Throwable { transactionManager.start(); Object result = null; try { result = method.invoke(proxy, objects); transactionManager.commit(); } catch (Exception e) { transactionManager.rollback(); } finally { transactionManager.close(); } return result; } }
通過調用該類的proxyFor方法,傳入需要被代理的對象(本例中為service對象),返回一個代理對象。此后,在調用代理對象的transfer方法時,會自動調用TransactionIvocationHandler的invoke方法,在該方法中,我們首先開始事務,然后執行:
result = method.invoke(proxy, objects);
上面一行代碼執行的是原service層的transfer方法,如果方法執行成功則commit,否則rollback事務。
由於與事務處理相關的代碼都被轉移到了代理對象中,在service層中我們只需調用DAO即可:
package davenkin.step5_transaction_proxy; import davenkin.BankService; import davenkin.step3_connection_holder.ConnectionHolderBankDao; import davenkin.step3_connection_holder.ConnectionHolderInsuranceDao; import javax.sql.DataSource; public class BareBankService implements BankService { private ConnectionHolderBankDao connectionHolderBankDao; private ConnectionHolderInsuranceDao connectionHolderInsuranceDao; public BareBankService(DataSource dataSource) { connectionHolderBankDao = new ConnectionHolderBankDao(dataSource); connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource); } public void transfer(final int fromId, final int toId, final int amount) { try { connectionHolderBankDao.withdraw(fromId, amount); connectionHolderInsuranceDao.deposit(toId, amount); } catch (Exception e) { throw new RuntimeException(); } } }
如何,上面的BareBankService中沒有任何事務處理的影子,我們只需關注核心業務邏輯即可。
然后在客戶代碼中,我們需要先創建代理對象(這在Spring中通常是通過配置實現的):
@Test public void transferFailure() throws SQLException { TransactionEnabledProxyManager transactionEnabledProxyManager = new TransactionEnabledProxyManager(new TransactionManager(dataSource)); BankService bankService = new BareBankService(dataSource); BankService proxyBankService = (BankService) transactionEnabledProxyManager.proxyFor(bankService); int toNonExistId = 3333; proxyBankService.transfer(1111, toNonExistId, 200); assertEquals(1000, getBankAmount(1111)); assertEquals(1000, getInsuranceAmount(2222)); }
在上面的測試代碼中,我們首先創建一個BareBankService對象,然后調用transactionEnabledProxyManager的proxyFor方法生成對原BareBankService對象的代理對象,最后在代理對象上調用transfer方法,測試運行成功。
可以看到,通過以上動態代理實現,BareBankService中的所有public方法都被代理了,即他們都被加入到事務中。這對於service層中的所有方法都需要和數據庫打交道的情況是可以的,本例即如此(有且只有一個transfer方法),然而對於service層中不需要和數據庫打交道的public方法,這樣做雖然也不會出錯,但是卻顯得多余。在下一篇文章中,我們將講到使用Java注解(annotation)的方式來聲明一個方法是否需要事務,就像Spring中的Transactional注解一樣。