Spring 代理模式解決事務
1. 前言
大家好,本小節(jié),我們學習代理模式解決轉賬過程中產(chǎn)生的事務問題,如有中途而來的童鞋,請先移步上一小節(jié)學習下問題的場景。
2. 實戰(zhàn)案例
2.1 實現(xiàn)思路介紹
1. 創(chuàng)建一個工具類,目的是用于管理數(shù)據(jù)庫的事務,提供事務的開啟,提交,回滾等操作;
2. 創(chuàng)建一個代理處理器類,目的是生成轉賬實現(xiàn)類的代理對象,對轉賬的業(yè)務方法提供增強,主要是在數(shù)據(jù)操作之前,和操作之后干點事,嘿嘿嘿;
3. 在 Spring 的配置文件中,通過 xml 文件的標簽實例化管理事務的工具類和生成代理對象的處理器類。
2.2 代碼實現(xiàn)
1. 創(chuàng)建事務管理器類
package com.offcn.transaction;
/**
* @Auther: wyan
* @Date: 2020-05-26 21:20
* @Description:
*/
import com.offcn.utils.ConnectionUtils;
/**
* 和事務管理相關的工具類,它包含了,開啟事務,提交事務,回滾事務和釋放連接
*/
public class TransactionManager {
//獲取數(shù)據(jù)庫連接的工具類
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 開啟事務
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事務
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滾事務
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 釋放連接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//還回連接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
代碼解釋:此工具類主要作用是對數(shù)據(jù)庫連接實現(xiàn)事務的開啟,提交以及回滾。至于何時開啟事務,何時提交事務,何時回滾事務,那就根據(jù)業(yè)務場景需要調用該類的方法即可。
2. 創(chuàng)建動態(tài)處理器
package com.offcn.utils;
import com.offcn.service.IAccountService;
import com.offcn.transaction.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Auther: wyan
* @Date: 2020-05-26 21:08
* @Description:
*/
public class TransactionProxyFactory {
//被代理的業(yè)務類接口
private IAccountService accountService;
//提供事務管理的工具類
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 獲取Service代理對象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事務的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//
Object rtValue = null;
try {
//1.開啟事務
txManager.beginTransaction();
//2.執(zhí)行操作
rtValue = method.invoke(accountService, args);
//3.提交事務
txManager.commit();
//4.返回結果
return rtValue;
} catch (Exception e) {
//5.回滾操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.釋放連接
txManager.release();
}
}
});
}
}
代碼解釋:
此類的核心代碼就是 getAccountService
方法,該方法返回代理業(yè)務類示例,而在代理對象的 invoke 方法內部,實現(xiàn)對原始被代理對象的增強。
方法的參數(shù)解釋如下:
- proxy: 該參數(shù)就是被代理的對象實例本身;
- method: 該參數(shù)是被代理對象正在執(zhí)行的方法對象;
- args: 該參數(shù)是正在訪問的方法參數(shù)對象。
在方法內部,method.invoke()
的方法調用,即表示被代理業(yè)務類的方法執(zhí)行,我們調用 txManager
的開啟事務方法。在 method.invoke()
方法執(zhí)行之后,調用提交事務的方法。
一旦執(zhí)行過程出現(xiàn)異常,在 catch
代碼塊中調用事務回滾的方法。這樣就保證了事務的原子性,執(zhí)行的任務,要么全部成功,要么全部失敗。
最終在 finally
的代碼塊中,調用釋放連接的方法。
3. 配置文件的修改:
添加事務管理的相關配置,完整配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Service -->
<bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao對象-->
<bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置數(shù)據(jù)源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--連接數(shù)據(jù)庫的必備信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/transmoney"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置Connection的工具類 ConnectionUtils -->
<bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils">
<!-- 注入數(shù)據(jù)源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務管理器-->
<bean id="txManager" class="com.offcn.transaction.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置beanfactory-->
<bean id="beanFactory" class="com.offcn.utils.TransactionProxyFactory">
<!-- 注入service -->
<property name="accountService" ref="accountService"></property>
<!-- 注入事務管理器 -->
<property name="txManager" ref="txManager"></property>
</bean>
<!--配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
</beans>
4. 測試類代碼
代碼解釋:
本測試代碼發(fā)生一個小變化,第 23 行的位置,多了一個注解 @Qualifier
。此注解的作用不知各位是否還記得,如果在 Spring 的容器中,出現(xiàn)多種同類型的 bean ,可以通過此注解指定引入的
實例,所以這里的 注解內的字符串 proxyAccountService
表示本 IAccountService
接口引入的實例為代理對象。那么為什么要引入代理對象呢?因為代理對象的方法內部已經(jīng)做了增強邏輯,通過 TransactionManager 類實現(xiàn)對事務的開啟,提交和回滾。
5. 測試結果:
為了測試效果更明顯,我們先把數(shù)據(jù)庫的數(shù)據(jù)還原為每人各 1000,如圖:
執(zhí)行代碼后結果:
當然還會繼續(xù)報錯,但是數(shù)據(jù)庫呢?上次是一個賬號減去了 100 塊錢,另外一個賬號卻沒有增加錢,這次我們來看看:
可以看到:賬號的金錢依然是原樣,這就說明事務的控制已經(jīng)生效了,保證了數(shù)據(jù)的一致性。
3. 小結
本小節(jié)學習了代理模式實現(xiàn)對事務的控制,加深了代理模式的優(yōu)點及作用:
- 職責清晰: 代理類與被代理類各司其職,互不干擾;
- 高擴展性: 代碼耦合性低,可以更加方便對方法做增強;
- 符合開閉原則: 系統(tǒng)具有較好的靈活性和可擴展性。