Spring 代理模式解決事務(wù)
1. 前言
大家好,本小節(jié),我們學(xué)習(xí)代理模式解決轉(zhuǎn)賬過程中產(chǎn)生的事務(wù)問題,如有中途而來的童鞋,請先移步上一小節(jié)學(xué)習(xí)下問題的場景。
2. 實(shí)戰(zhàn)案例
2.1 實(shí)現(xiàn)思路介紹
1. 創(chuàng)建一個(gè)工具類,目的是用于管理數(shù)據(jù)庫的事務(wù),提供事務(wù)的開啟,提交,回滾等操作;
2. 創(chuàng)建一個(gè)代理處理器類,目的是生成轉(zhuǎn)賬實(shí)現(xiàn)類的代理對象,對轉(zhuǎn)賬的業(yè)務(wù)方法提供增強(qiáng),主要是在數(shù)據(jù)操作之前,和操作之后干點(diǎn)事,嘿嘿嘿;
3. 在 Spring 的配置文件中,通過 xml 文件的標(biāo)簽實(shí)例化管理事務(wù)的工具類和生成代理對象的處理器類。
2.2 代碼實(shí)現(xiàn)
1. 創(chuàng)建事務(wù)管理器類
package com.offcn.transaction;
/**
* @Auther: wyan
* @Date: 2020-05-26 21:20
* @Description:
*/
import com.offcn.utils.ConnectionUtils;
/**
* 和事務(wù)管理相關(guān)的工具類,它包含了,開啟事務(wù),提交事務(wù),回滾事務(wù)和釋放連接
*/
public class TransactionManager {
//獲取數(shù)據(jù)庫連接的工具類
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 開啟事務(wù)
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事務(wù)
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滾事務(wù)
*/
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ù)庫連接實(shí)現(xiàn)事務(wù)的開啟,提交以及回滾。至于何時(shí)開啟事務(wù),何時(shí)提交事務(wù),何時(shí)回滾事務(wù),那就根據(jù)業(yè)務(wù)場景需要調(diào)用該類的方法即可。
2. 創(chuàng)建動(dò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è)務(wù)類接口
private IAccountService accountService;
//提供事務(wù)管理的工具類
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() {
/**
* 添加事務(wù)的支持
*
* @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.開啟事務(wù)
txManager.beginTransaction();
//2.執(zhí)行操作
rtValue = method.invoke(accountService, args);
//3.提交事務(wù)
txManager.commit();
//4.返回結(jié)果
return rtValue;
} catch (Exception e) {
//5.回滾操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.釋放連接
txManager.release();
}
}
});
}
}
代碼解釋:
此類的核心代碼就是 getAccountService
方法,該方法返回代理業(yè)務(wù)類示例,而在代理對象的 invoke 方法內(nèi)部,實(shí)現(xiàn)對原始被代理對象的增強(qiáng)。
方法的參數(shù)解釋如下:
- proxy: 該參數(shù)就是被代理的對象實(shí)例本身;
- method: 該參數(shù)是被代理對象正在執(zhí)行的方法對象;
- args: 該參數(shù)是正在訪問的方法參數(shù)對象。
在方法內(nèi)部,method.invoke()
的方法調(diào)用,即表示被代理業(yè)務(wù)類的方法執(zhí)行,我們調(diào)用 txManager
的開啟事務(wù)方法。在 method.invoke()
方法執(zhí)行之后,調(diào)用提交事務(wù)的方法。
一旦執(zhí)行過程出現(xiàn)異常,在 catch
代碼塊中調(diào)用事務(wù)回滾的方法。這樣就保證了事務(wù)的原子性,執(zhí)行的任務(wù),要么全部成功,要么全部失敗。
最終在 finally
的代碼塊中,調(diào)用釋放連接的方法。
3. 配置文件的修改:
添加事務(wù)管理的相關(guān)配置,完整配置文件如下:
<?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>
<!-- 配置事務(wù)管理器-->
<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>
<!-- 注入事務(wù)管理器 -->
<property name="txManager" ref="txManager"></property>
</bean>
<!--配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
</beans>
4. 測試類代碼
代碼解釋:
本測試代碼發(fā)生一個(gè)小變化,第 23 行的位置,多了一個(gè)注解 @Qualifier
。此注解的作用不知各位是否還記得,如果在 Spring 的容器中,出現(xiàn)多種同類型的 bean ,可以通過此注解指定引入的
實(shí)例,所以這里的 注解內(nèi)的字符串 proxyAccountService
表示本 IAccountService
接口引入的實(shí)例為代理對象。那么為什么要引入代理對象呢?因?yàn)榇韺ο蟮姆椒▋?nèi)部已經(jīng)做了增強(qiáng)邏輯,通過 TransactionManager 類實(shí)現(xiàn)對事務(wù)的開啟,提交和回滾。
5. 測試結(jié)果:
為了測試效果更明顯,我們先把數(shù)據(jù)庫的數(shù)據(jù)還原為每人各 1000,如圖:
執(zhí)行代碼后結(jié)果:
當(dāng)然還會繼續(xù)報(bào)錯(cuò),但是數(shù)據(jù)庫呢?上次是一個(gè)賬號減去了 100 塊錢,另外一個(gè)賬號卻沒有增加錢,這次我們來看看:
可以看到:賬號的金錢依然是原樣,這就說明事務(wù)的控制已經(jīng)生效了,保證了數(shù)據(jù)的一致性。
3. 小結(jié)
本小節(jié)學(xué)習(xí)了代理模式實(shí)現(xiàn)對事務(wù)的控制,加深了代理模式的優(yōu)點(diǎn)及作用:
- 職責(zé)清晰: 代理類與被代理類各司其職,互不干擾;
- 高擴(kuò)展性: 代碼耦合性低,可以更加方便對方法做增強(qiáng);
- 符合開閉原則: 系統(tǒng)具有較好的靈活性和可擴(kuò)展性。