In the previous article in this series, we talked about using the Template mode for transaction management, which is certainly a good method, but not so perfect is that we still need to write the code related to transaction processing in the service layer, that is, we need to declare a TransactionTemplate in the service layer. In this article, we will use the Dynamic Proxy function provided by Java to complete transaction processing, you will see that no transaction processing code exists at either the service layer or the DAO layer, that is, they are not aware of the existence of transaction processing. Using Dynamic proxy to complete transaction processing is also a typical application of AOP.
This is a series of articles about Java transaction processing. Please download the github source code in the following ways:
Git clone https://github.com/davenkin/java_transaction_workshop.git
The basic principle of Java Dynamic proxy is: the proxy object needs to implement an interface (this is the premise), the proxy object will intercept the method call of the proxy object, you can discard the method implementation of the proxy object and complete other functions. You can also add some additional functions before and after being called by the proxy object method. In this article, we will intercept the transfer method on the service layer, add transaction preparation before calling it, and then call the original transfer method, then, the commit or rollback is determined based on whether the transfer method is successfully executed.
First define a TransactionEnabledProxyManager class:
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; }}
Call the proxyFor method of this class to pass in the object to be proxy (in this example, it is a service object) and return a proxy object. After that, when the transfer method of the proxy object is called, The invoke method of TransactionIvocationHandler is automatically called. In this method, we start the transaction and then execute:
result = method.invoke(proxy, objects);
The above line of code executes the transfer method of the original service layer. If the method is successfully executed, commit; otherwise, the rollback transaction will be executed.
Since the Code related to transaction processing is transferred to the proxy object, we only need to call DAO at the service layer:
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(); } }}
The preceding BareBankService does not have any transaction processing shadows. We only need to focus on the core business logic.
Then in the Customer Code, we need to create a proxy object first (which is usually implemented through configuration in 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)); }
In the above test code, we first create a BareBankService object, then call the proxyFor method of transactionEnabledProxyManager to generate a proxy object for the original BareBankService object, and finally call the transfer method on the proxy object, the test runs successfully.
As you can see, through the above dynamic proxy implementation, all the public methods in BareBankService are proxies, that is, they are all added to the transaction. This is acceptable when all methods in the service layer need to deal with the database. This is the case in this example (there is only one transfer method ), however, the public method in the service layer that does not need to deal with the database does not make any mistakes, but it seems redundant. In the next article, we will talk about using the Java annotation (annotation) method to declare whether a method requires transactions, just like the Transactional annotation in Spring.