亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Spring 代理模式解決事務

1. 前言

大家好,本小節,我們學習代理模式解決轉賬過程中產生的事務問題,如有中途而來的童鞋,請先移步上一小節學習下問題的場景。

2. 實戰案例

2.1 實現思路介紹

1. 創建一個工具類,目的是用于管理數據庫的事務,提供事務的開啟,提交,回滾等操作;

2. 創建一個代理處理器類,目的是生成轉賬實現類的代理對象,對轉賬的業務方法提供增強,主要是在數據操作之前,和操作之后干點事,嘿嘿嘿;

3. 在 Spring 的配置文件中,通過 xml 文件的標簽實例化管理事務的工具類和生成代理對象的處理器類。

2.2 代碼實現

1. 創建事務管理器類

package com.offcn.transaction;

/**
 * @Auther: wyan
 * @Date: 2020-05-26 21:20
 * @Description:
 */

import com.offcn.utils.ConnectionUtils;

/**
 * 和事務管理相關的工具類,它包含了,開啟事務,提交事務,回滾事務和釋放連接
 */
public class TransactionManager {
	//獲取數據庫連接的工具類
    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();
        }
    }
}

代碼解釋:此工具類主要作用是對數據庫連接實現事務的開啟,提交以及回滾。至于何時開啟事務,何時提交事務,何時回滾事務,那就根據業務場景需要調用該類的方法即可。

2. 創建動態處理器

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 {
	//被代理的業務類接口
    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.執行操作
                            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 方法,該方法返回代理業務類示例,而在代理對象的 invoke 方法內部,實現對原始被代理對象的增強。

方法的參數解釋如下:

  1. proxy: 該參數就是被代理的對象實例本身;
  2. method: 該參數是被代理對象正在執行的方法對象;
  3. args: 該參數是正在訪問的方法參數對象。

在方法內部,method.invoke() 的方法調用,即表示被代理業務類的方法執行,我們調用 txManager 的開啟事務方法。在 method.invoke() 方法執行之后,調用提交事務的方法。

一旦執行過程出現異常,在 catch 代碼塊中調用事務回滾的方法。這樣就保證了事務的原子性,執行的任務,要么全部成功,要么全部失敗。

最終在 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>
    <!-- 配置數據源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--連接數據庫的必備信息-->
        <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">
        <!-- 注入數據源-->
        <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. 測試類代碼

圖片描述

代碼解釋:

本測試代碼發生一個小變化,第 23 行的位置,多了一個注解 @Qualifier 。此注解的作用不知各位是否還記得,如果在 Spring 的容器中,出現多種同類型的 bean ,可以通過此注解指定引入的

實例,所以這里的 注解內的字符串 proxyAccountService 表示本 IAccountService 接口引入的實例為代理對象。那么為什么要引入代理對象呢?因為代理對象的方法內部已經做了增強邏輯,通過 TransactionManager 類實現對事務的開啟,提交和回滾。

5. 測試結果:

為了測試效果更明顯,我們先把數據庫的數據還原為每人各 1000,如圖:

圖片描述

執行代碼后結果:

圖片描述

當然還會繼續報錯,但是數據庫呢?上次是一個賬號減去了 100 塊錢,另外一個賬號卻沒有增加錢,這次我們來看看:

圖片描述

可以看到:賬號的金錢依然是原樣,這就說明事務的控制已經生效了,保證了數據的一致性。

3. 小結

本小節學習了代理模式實現對事務的控制,加深了代理模式的優點及作用:

  1. 職責清晰: 代理類與被代理類各司其職,互不干擾;
  2. 高擴展性: 代碼耦合性低,可以更加方便對方法做增強;
  3. 符合開閉原則: 系統具有較好的靈活性和可擴展性。