事务控制,动态代理,动态代理实现事务控制

事务控制,动态代理,动态代理实现事务控制

环境:
Idea:2019.3.1
系统:windows10 家庭版
Jdk: 8
spring:5.0.3 release
spring文档
项目代码

一、事务控制

背景

操作账户的项目结构如下

正常流程是
首先创建一个账户业务层对象accountServiceImpl
再创建一个持久层对象accountDaoImpl
最后accountServiceImpl调用accountDao通过ConnectionUtils对象获取Conncetion连接,再执行操作,这就有个问题,按这种来写,如果有一个功能需要多条代码,例如,转账,A向B转账

    /**
     * 转账
     * @param sourceName:转出账户名称
     * @param targetName:转入账户名称
     * @param money:转账金额
     */
 public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1.根据名称查询转出账户
            Account sourceAccount = accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
            Account targetAccount = accountDao.findAccountByName(targetName);
            //3.转出账户减钱
            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            //4.转入账户加钱
            targetAccount.setMoney(targetAccount.getMoney() + money);
            //5.更新转出账户
            accountDao.updateAccount(sourceAccount);
            //6.更新转入账户
            accountDao.updateAccount(targetAccount);
        }catch (Exception e){

        }

我们在这6条操作中加入一条int sum = 1/0

            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            int sum = 1/0        <--------------------------------------------------这里
            //4.转入账户加钱
            targetAccount.setMoney(targetAccount.getMoney() + money);

执行程序后我们发现程序出错,转账失败,但是转出账户已经减钱了,这是因为这6条语句的事务都是相互独立的,后面语句出错也不会影响前面的事务在出错前已经提交了,我们要解决这个问题,就要将这些语句变成一个事务

事务控制

1、新建一个事务控制器类TransactionManager

该类的内容如下

import java.sql.SQLException;

/**
 * 和事务相关的工具类,它包含了开启事务,提交事务,回滚事务,释放连接
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

    /**
     * 开始事务
     */
    public void beginTranscation(){
        try {
        //关闭事务的自动提交,即改为手动提交
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close();//并不是真的关闭,是将conn连接还回连接池中
            connectionUtils.removeConnection();//解绑,将连接对象和线程解绑
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
}

2、修改ConnectionUtils,用ThreadLoacl<>容器将Connection连接与当前线程绑定,要使一个线程中只有一个能控制事务的对象

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {
    //threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    //数据源
    private DataSource dataSource;

    /**
    *获取connection对象
    */
    public Connection getThreadConnection(){
        try {
        //1.先从ThreadLocal上获取Connection对象
        Connection conn = tl.get();
        //2.判断当前线程上是否有连接
        if(conn == null){
            //3.从数据源中获取一个连接,并且存入ThreadLocal中
            conn = dataSource.getConnection();
            tl.set(conn);
        }
        //4.返回当前线程上的连接
            return conn;
    }catch (SQLException e){
        throw new RuntimeException();
    }
    }

    /**
     * 将线程和连接解绑
     */
    public void removeConnection(){
        tl.remove();
    }
    /**
    *设置数据源
    */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

3、在业务层AccountServiceImpl中加入事务

    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //一.开启事务
            transactionManager.beginTranscation();
            //二.执行操作
            //1.根据名称查询转出账户
            Account sourceAccount = accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
            Account targetAccount = accountDao.findAccountByName(targetName);
            //3.转出账户减钱
            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            //4.转入账户加钱
            targetAccount.setMoney(targetAccount.getMoney() + money);
            //5.更新转出账户
            accountDao.updateAccount(sourceAccount);
            //6.更新转入账户
            accountDao.updateAccount(targetAccount);
            //三.提交事务
            transactionManager.commit();
            //四.返回结果
        }catch (Exception e){
            //五.回滚事务
            transactionManager.rollback();
        }finally {
            //六.释放连接
            transactionManager.release();
        }
    }

将业务操作代码放在第二步,当业务操作代码出问题,事务不会提交,相反,一切正常,事务就会提交,这样就解决了之前的事务问题

二、动态代理

特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
    1.基于接口的动态代理
    2.基于子类的动态代理
基于接口的动态代理
    1.涉及的类:
        Proxy
    2.提供者:
        JDK官方
    3.如何创建代理对象:
        使用Proxy类中的newProxyInstance方法
    4.创建基于接口的代理对象的要求:
        被代理类最少实现一个接口,如果没有则不能使用
    5.newProxyInstance方法参数:
        ClassLoader:类加载器,用于加载代理对象字节码的,和被代理对象使用相同的类加载器,固定写法,类.getClass().getClassLoader();
        Class[]:它是用于让代理对象和被代理对象有相同方法,固定写法,类.getClass().getInterfaces();
        InvocationHandler:它是让我们写如何代理。我们一般都是写一些该接口的实现类,通常情况下都是匿名内部类,但不是必须的,此接口的实现类都是谁用谁写

        Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler(){
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
        })
例子

proxy类直接导入使用即可

Producer接口
/**
 * 对生产厂家要求的接口
 */
public interface IProducer {
    /**
     * 销售
     * @param money
     *
     */
    public void saleProduct(float money);
    /**
     * 售后
     * @param money
     */
    public void afterService(float money);
}
Producer类
/**
 * 一个生产者
 */
public class Producer implements IProducer{
    /**
     * 销售方法
     * @param money
     *
     */
    public void saleProduct(float money){
        System.out.println("销售产品,拿到钱:"+money);
    }

    /**
     * 售后方法
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,拿到钱:" + money);
    }
}

测试类Client
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模拟一个消费着
 */
public class Client {
    public static void main(String[] args) {
        //创建一个生产商
        final Producer producer = new Producer();

        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.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 returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float)args[0];
                //2.判断当前方法是不是销售
                if("saleProduct".equals(method.getName())){
                    //提成20%
                    returnValue = method.invoke(producer,money*0.8f);
                }
                return returnValue;
            }
        });
        proxyProducer.saleProduct(10000f);
    }
}

运行结果

基于子类的动态代理
        1.涉及的类:
            Enhancer
        2.提供者:
            第三方cglib库
        3.如何创建代理对象:
            使用Enhancer类中的create方法
        4.创建基于子类的代理对象的要求:
            被代理对象不能是最终类
        5.create方法参数:
            Class:字节码,用于指定被代理对象的字节码,固定写法,类.getClass()
            Callback:用于让我们写如何代理,一般写一个该接口的实现类,常用MethodInterceptor,通常情况下都是写匿名内部类,但不是必须的,谁用谁写

            Enhancer.create(producer.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 
            }
        });
例子

首先导入Enhancer类所需cglib库的坐标

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
Producer类
package com.cbw.cglib;


/**
 * 一个生产者
 */
public class Producer {
    /**
     * 销售
     * @param money
     *
     */
    public void saleProduct(float money){
        System.out.println("销售产品,拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,拿到钱:" + money);
    }
}

测试类Client
package com.cbw.cglib;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;


import java.lang.reflect.Method;


/**
 * 模拟一个消费着
 */
public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();

        /*
        *动态代理:
        *   特点:字节码随用随创建,随用随加载
        *   作用:不修改源码的基础上对方法增强
        *   分类:
        *       1.基于接口的动态代理
        *       2.基于子类的动态代理
        * 这里我们使用基于子类的动态代理:
        *       涉及的类:Enhancer
        *       提供者:第三方cglib库
        * 学习过程:
        *       1.如何创建代理对象:
        *           使用Enhancer类中的create方法
        *       2.创建代理对象的要求:
        *           被代理对象不能是最终类
        *       3.create方法参数:
        *           Class:字节码,用于指定被代理对象的字节码,固定写法,类.getClass()
        *           Callback:用于让我们写如何代理,一般写一个该接口的实现类,常用MethodInterceptor,通常情况下都是写匿名内部类,但不是必须的,谁用谁写
         */
        Producer proxyProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {

            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //这里进行方法增强,比如,这里我们可以抽取部分提成
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float)args[0];
                //2.判断当前方法是不是销售
                if("saleProduct".equals(method.getName())){
                    returnValue = method.invoke(producer,money*0.8f);
                }
                return returnValue;
            }
        });
        proxyProducer.saleProduct(10000f);

    }
}

运行结果

AccountServiceImpl是之前事务所用到的,所以里面每个方法都套了一遍事务
而AccountServiceImpl_new则是无添加事务的普通service,我们使用这个,然后其他文件照旧

三、动态代理实现事务控制

我们使用基于接口的动态代理,项目结构如下

1、在之前事务的项目下新建BeanFactory类,这是创建service代理对象的工厂,我们使用动态代理将事务放在代理对象的增强方法invoke中

import com.cbw.service.IAccountService;
import com.cbw.utils.TransactionManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用于创建service的代理对象的工厂
 */
public class BeanFactory {
    //accountService对象
    private IAccountService accountService;
    //事务管理器对象
    private TransactionManager transactionManager;

    /**
     * 用于注入transactionManager的set方法
     * @param transactionManager
     */
    public final void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    /**
     * 用于注入accountService的set方法
     * @param accountService
     */
    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.开启事务
                    transactionManager.beginTranscation();
                    //2.执行操作
                    rtValue = method.invoke(accountService,args);
                    //3.提交事务
                    transactionManager.commit();
                    //4.返回结果
                    return rtValue;
                }catch (Exception e){
                    //5.回滚事务
                    transactionManager.rollback();
                }finally {
                    //6.释放连接
                    transactionManager.release();
                }
                return rtValue;
            }
        });
    }
}

根据上面事务和动态代理的知识,不难理解这个类。
这个类有一个核心方法,就是getAccountService() ,用于获取accountService的代理对象,这里简写了,直接return (IAccountService) Proxy.newProxyInstance(…..);这个对象就是accountService的代理对象
然后 invoke(….) 方法中,首先定义一个rtValue对象,这个是用来接收方法的返回值的,然后执行之前写的事务6步骤,在第2步执行method.invoke(accountService,args)方法,accountService参数是被代理的对象,args则是该方法的参数数组,执行完方法后返回值由rtValue来接收,然后第4步返回返回值,这里我们用Object类型来定义rtValue,所以在外面的return还要强转一下

补充:
因为增强方法实在匿名内部类中,又因为我们对象是用set方法注入的,所以我们要在accountService和transactionManager两个对象的set方法加上final关键字

2、applicationContext.xml文件

<?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">

<!--    1.配置代理的service对象,使用的是普通工厂的方法获取bean对象-->
        <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

<!--    2.配置factory对象-->
        <bean id="beanFactory" class="com.cbw.factory.BeanFactory">
            <!--        注入service对象-->
                <property name="accountService" ref="accountService"></property>
            <!--        注入事务管理器对象,这个在第8步-->
                <property name="transactionManager" ref="transactionManager"></property>
        </bean>

<!--    3.配置service对象-->
        <bean id="accountService" class="com.cbw.service.impl.AccountServiceImpl_new">
            <!--        注入accountDao-->
                <property name="accountDao" ref="accountDao"></property>
            <!--注入事务管理器对象-->
                <property name="transactionManager" ref="transactionManager"></property>-->
        </bean>

<!--    4.配置Dao对象-->
        <bean id="accountDao" class="com.cbw.dao.impl.AccountDaoImpl">
            <!--        注入runner-->
                <property name="runner" ref="runner"></property>
            <!--注入ConnectionUtils-->
                <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>

<!--    5.配置runner对象-->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

<!--    6.配置数据源-->
        <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/myspringspace?useUnicode=true&amp;characterEncoding=utf8"></property>
                <property name="user" value="root"></property>
                <property name="password" value="123456"></property>
        </bean>

<!--    7.配置ConncetionUtils工具类-->
        <bean id="connectionUtils" class="com.cbw.utils.ConnectionUtils">
            <!--        注入连接池-->
                <property name="dataSource" ref="dataSource"></property>
        </bean>

<!--    8.创建事务管理器对象-->
        <bean id="transactionManager" class="com.cbw.utils.TransactionManager">
            <!--        注入connectionUtils对象-->
                <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

用了c3p0连接池,还有apache的dbUtils工具包,实现的内容是一样的,用原生jdbc也是一样的效果,除此之外,没什么要补充的

补充:
第1步中获取的bean是accountService的代理对象所以也是IAccountService接口的实现类,再加上第3步中,我们又配置了一个具有相同接口的bean对象,所以我们在使用@Autowried自动注入的时候,要加上@Qualifier(“id”)指定注入的bean类型,不然会出错

3、TestAccount测试类

Junit整合spring在上一个笔记

import com.cbw.domain.Account;
import com.cbw.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * 使用junit单元测试测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class accountTest {
    @Autowired
    @Qualifier("proxyAccountService")
    private IAccountService accountService;

    /**
     * 测试转账方法
     */
    @Test
    public void testTransfer(){
        System.out.println("转账前");
        testFindAll();
        accountService.transfer("aaa","bbb",200f);
        System.out.println("转账后");
        testFindAll();
    }

    /**
     * 测试查找全部方法
     */
    @Test
    public void testFindAll(){
        List<Account> accountList = accountService.findAllAccount();
        System.out.println(accountList);
    }
}

测试aaa给bbb转账200块,测试结果

转账成功,忽略中间的日志
我们再来测试一下,操作如果出问题,事务功能能否实现,在accountServiceImpl_new文件的transfer方法中加入 int i=1/0;

记住,aaa的钱是800,bbb是1200,如果转账出错,不会变的,测试结果

钱并没有变动,奇怪的是没有报错1/by zero


 上一篇
AOP的概念与spring中的AOP AOP的概念与spring中的AOP
AOP的概念与spring中的AOP 环境:Idea:2019.3.1系统:windows10 家庭版Jdk: 8spring:5.0.3 releasespring文档项目代码 一、AOP的概念1.什么是AOPAOP:全称是Aspect
2020-04-10
下一篇 
spring中的事务控制 spring中的事务控制
spring中的事务控制 环境:Idea:2019.3.1系统:windows10 家庭版Jdk: 8spring:5.0.3 releasespring文档项目代码 一.概述1.JavaEE体系进行分层开发,事务处理位于业务层,Spri
2020-04-10
  目录