在本系列的上一篇文章中,我們講到了使用動態代理的方式完成事務處理,這種方式將service層的所有public方法都加入到事務中,這顯然不是我們需要的,需要代理的只是那些需要操作數據庫的方法。在本篇中,我們將講到如何使用Java注解(Annotation)來標記需要事務處理的方法。
這是一個關於Java事務處理的系列文章,請通過以下方式下載github源代碼:
git clone https://github.com/davenkin/java_transaction_workshop.git
首先定義Transactional注解:
package davenkin.step6_annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Transactional { }
使用注解標記事務的基本原理為:依然使用上一篇中講到的動態代理的方式,只是在InvocationHandler的invoke方法中,首先判斷被代理的方法是否標記有Transactional注解,如果沒有則直接調用method.invoke(proxied, objects),否則,先准備事務,在調用method.invoke(proxied, objects),然后根據該方法是否執行成功調用commit或rollback。定義TransactionEnabledAnnotationProxyManager如下:
package davenkin.step6_annotation; import davenkin.step3_connection_holder.TransactionManager; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TransactionEnabledAnnotationProxyManager { private TransactionManager transactionManager; public TransactionEnabledAnnotationProxyManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public Object proxyFor(Object object) { return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new AnnotationTransactionInvocationHandler(object, transactionManager)); } } class AnnotationTransactionInvocationHandler implements InvocationHandler { private Object proxied; private TransactionManager transactionManager; AnnotationTransactionInvocationHandler(Object object, TransactionManager transactionManager) { this.proxied = object; this.transactionManager = transactionManager; } public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable { Method originalMethod = proxied.getClass().getMethod(method.getName(), method.getParameterTypes()); if (!originalMethod.isAnnotationPresent(Transactional.class)) { return method.invoke(proxied, objects); } transactionManager.start(); Object result = null; try { result = method.invoke(proxied, objects); transactionManager.commit(); } catch (Exception e) { transactionManager.rollback(); } finally { transactionManager.close(); } return result; } }
可以看到,在AnnotationTransactionInvocationHandler的invoke方法中,我們首先獲得原service的transfer方法,然后根據originalMethod.isAnnotationPresent(Transactional.class)判斷該方法是否標記有Transactional注解,如果沒有,則任何額外功能都不加,直接調用原來service的transfer方法;否則,將其加入到事務處理中。
在service層中,我們只需將需要加入事務處理的方法用Transactional注解標記就行了:
package davenkin.step6_annotation; import davenkin.BankService; import davenkin.step3_connection_holder.ConnectionHolderBankDao; import davenkin.step3_connection_holder.ConnectionHolderInsuranceDao; import javax.sql.DataSource; public class AnnotationBankService implements BankService { private ConnectionHolderBankDao connectionHolderBankDao; private ConnectionHolderInsuranceDao connectionHolderInsuranceDao; public AnnotationBankService(DataSource dataSource) { connectionHolderBankDao = new ConnectionHolderBankDao(dataSource); connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource); } @Transactional 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(); } } }
然后執行測試:
@Test public void transferFailure() throws SQLException { TransactionEnabledAnnotationProxyManager transactionEnabledAnnotationProxyManager = new TransactionEnabledAnnotationProxyManager(new TransactionManager(dataSource)); BankService bankService = new AnnotationBankService(dataSource); BankService proxyBankService = (BankService) transactionEnabledAnnotationProxyManager.proxyFor(bankService); int toNonExistId = 3333; proxyBankService.transfer(1111, toNonExistId, 200); assertEquals(1000, getBankAmount(1111)); assertEquals(1000, getInsuranceAmount(2222)); }
測試運行成功,如果將AnnotationBankService中transfer方法的Transactional注解刪除,那么以上測試將拋出RuntimeException異常,該異常為transfer方法中我們人為拋出的,也即由於此時沒有事務來捕捉異常,程序便直接拋出該異常而終止運行。在下一篇(本系列最后一篇)文章中,我們將講到分布式事務的一個入門例子。