07.银行转账案例

2.案例中添加转账方法并演示事务问题
新建一个工程,仍然复制以前的代码在这个新工程中,现在是一个银行转账案例,需要添加一个转账的方法.
我们在业务层添加一个转账方法.
在IAccountService中添加transfer接口,它有三个参数,分别是转账人名称,收款方名称,以及转账金额


IAccountService

在AccountServiceImpl中完善它,这个方法要实现的功能是
1.通过名称找到付款方
2.通过名称找到收款方
3.付款方减钱
4.收款方加钱
5.更新账户
可以看到需要一个通过名称找到对象的功能,我们在IAccountDao新添加这个接口,在AccountDaoImpl实现,


image.png

这个方法返回的是Account类型,这个方法是有要求的,根据名称查询账户,要求考虑一下返回的结果集情况,如果返回的结果集是多个,抛出异常,如果返回的结果集只有一个,就返回这个结果
package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    private QueryRunner queryRunner;
    public void setQueryRunner(org.apache.commons.dbutils.QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    public List<Account> findAllAccount() {
        try {
            return queryRunner.query("select * from account", new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public Account findAccountbyId(Integer accountId) {
        try {
            return queryRunner.query("select * from account where id = ? ", new BeanHandler<Account>(Account.class),accountId);
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void saveAccount(Account account) {
        try {
            queryRunner.update("insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void updateAccount(Account account) {
        try {
           // queryRunner.update("update  account set name = ?,money = ? where id = ?",account.getId(),account.getName(),account.getMoney());
            queryRunner.update( "update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            queryRunner.update("delete from account where id = ? ",accountId);
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }
    /**
     * 根据名称查找账户
     * 如果有唯一的一个结果就返回,如果没有就返回null
     * 如果结果集超过一个就抛异常
     * @param
     * @return
     */
    @Override
    public Account findAccountbyname(String accountName) {
        try{
            List<Account> accountList = queryRunner.query("select * from account where name= ?", new BeanListHandler<Account>(Account.class),accountName);
            if (accountList == null || accountList.size() == 0){
                    return null;
            }
           if (accountList.size()>1){
               throw new RuntimeException("结果集不唯一");
           }
           return accountList.get(0);
        }catch (Exception e){
            throw new RuntimeException();
        }
    }
}

接下来在AccountServiceImpl完善我们的tranfer方法

package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.dao.impl.AccountDaoImpl;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public Account findAccountbyId(Integer accountId) {
        return accountDao.findAccountbyId(accountId);
    }

    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    public void deleteAccount(Integer accountId) {
        accountDao.deleteAccount(accountId);
    }

    /**
     * 1.根据名称查询转出账户
     * 2.根据名称查询转入账户
     * 3.转出账户减钱
     * 4.转入账户加钱
     * 5.更新转出账户
     * 6.更新转入账户
     * @param SourceName
     * @param targetName
     * @param moeny
     */
    @Override
    public void transfer(String SourceName, String targetName, Float moeny) {
        //根据名称查询转出账户
        Account payUser = accountDao.findAccountbyname(SourceName);
        //根据名称查询转入账户
        Account recvUser = accountDao.findAccountbyname(targetName);
        //转出账户减钱
        payUser.setMoney(payUser.getMoney()-moeny);
        //转入账户加钱
        recvUser.setMoney(recvUser.getMoney()+moeny);
        //更新转出账户
        accountDao.updateAccount(payUser);
        //更新转入账户
        accountDao.updateAccount(recvUser);
    }
}

接下来我们在test方法中测试一下,写一个testTransfer方法,可以看到执行成功,在数据库中,aaa少了100,b多了100

public class AccountServiceTest {
    private IAccountService accountService;
    @Test
    public void testTransfer(){
        accountService.transfer("aaa","bbb",100f);
    }
  }

(执行transfer方法后数据库的账户数目并没有变化,后来发现是我在dao的时候参数顺序不对)


update账户的时候总是失败,debug也没看出原因,对比了下源码发现参数顺序不对

但是这样的写法是有问题的,
如果我们在更新账号的时候写一段错误代码进去,比如 int i = 1/0,引起程序报错,我们就发现,在int i = 1/0之前的代码已执行,而int i = 1/0以后的代码因为报错终止了程序进行,更新收款人账号这一步就未执行.

有异常的程序

如何解决这种问题?我们忽略了什么?

是因为没有事务?如果要是没有事务的话,就代表着无法提交,那以前的案例的增删改的方法,应该是都是都无法执行的,因为没有事务就无法提交,无法提交只能回滚,很显然,我们之前代码的增删改查都执行成功了,换句话说,不是没有事务造成的.
是什么造成的?从这段代码得知
QueryRunner对象每次都会创建新的,并且每次都从数据源中拿出一个连接,接下来我们可以看到它和数据库交互了几次,一共四次,每个Connection都有自己的独立事务,这些连接执行------>成功------>提交.直到报错信息的出现
解决这个问题的思路应该是让他们只有一个Connection,这个Connection如果成功,这些操作都能成功,如果失败,就都失败.

事务控制

这个时候需要使用ThreadLocal对象把Connection和当前的线程绑定,从而使一个线程中只有一个控制事务的对象.
通过这个分析,我们需要对代码进行一定的调整
第一个点,AccountServiceImpl中没有事务的控制,或者说事务的控制都不在它应该在的地方.
事务的控制应该都在业务层,而以前的代码,事务都是在持久层,这是因为之前是为了演示Spring基于注解的IOC,现在是为了AOP的概念的展示,这个例子有利于Aop的优势
接下来来看一下,怎么实现事务控制
需要一个ConnectionUtils的工具类,用它来获取线程上的连接

public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<>();
    private DataSource dataSource;

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

接下来再编写TransationManager类,用于事务控制,控制事务是靠Connection的手动提交改为自动提交 然后再通过comit和rollback方法对事务进行提交,需要4个功能,分别是开启事务,把手动提交改为自动提交,提交事务,回滚事务,释放事务,如何去执行呢?首先得有Connection 这个Connection必须是线程上的Connection,也就是刚刚我们编写的的ConnectionUtil类获取到的线程连接,开启事务就很简单了,setAutoCommit()为false就行了,其他方法也是同样的道理.

package util;

import org.springframework.beans.factory.annotation.Autowired;

import java.sql.Connection;
import java.sql.SQLException;

public class TransationManager {
    @Autowired
    private ConnectionUtils connection;
    //开启事务
    public void beginTransation(){
        try {
            connection.getConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //提交事务
    public void CommitTransation(){
        try {
            connection.getConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //回滚事务
    public void RollBackTransation(){
        try {
            connection.getConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //释放连接
    public void releaseTransation(){
        try {
            connection.getConnection().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

这个时候,现在实现了当前线程上有这么一个连接了,这里有一个小细节:我们的连接都使用了连接池,也就是在程序初始化的时候,就集中创建多个数据库连接,并把他们集中管理,供程序使用,这样可以保证较快的数据库读写速度,并且安全可靠。
线程池,当Tomcat启动时会初始化一大堆的线程中在容器里,线程池中的线程就像连接池里的连接一样,调用close方法并不是真正的关闭,而是还入连接池中.线程在用完了,也是还入线程池中.
如果是这样的话,线程上是绑着一个连接的,当我们再从线程中获取连接时,线程上的连接依然存在,只是关闭了,下次再获取线程时,上面是有连接的,但是已经不能用了.所以我们要对连接进行一个解绑的操作,这个解绑操作在ConnectionUtil中写上

    /**
     * 解绑
     */
    public void RemoveConnection(){
        tl.remove();
    }
}

写完了TransationManger类,我们需要用到它来进行事务管理,我们已经提到了,事务应该发生在业务层,所以我们在业务层上添加事务,开启事务===>执行====>提交,如果有异常就回滚=====>最后释放连接

package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.util.TransationManager;
import org.springframework.stereotype.Service;
import java.util.List;

public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    private TransationManager transationManager;

    public List<Account> findAllAccount() {
        try {
            //开启事务
            transationManager.beginTransation();
            //执行操作
            List<Account> accountList = accountDao.findAllAccount();
            //提交事务
            transationManager.CommitTransation();
            return accountList;
        } catch (Exception e) {
            //回滚事务
            transationManager.RollBackTransation();
            throw new RuntimeException();
        } finally {
            //释放连接
            transationManager.releaseTransation();
        }
    }

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public Account findAccountbyId(Integer accountId) {
        try {
            //开启事务
            transationManager.beginTransation();
            //执行操作
            Account account = accountDao.findAccountbyId(accountId);
            //提交事务
            transationManager.CommitTransation();
            //返回结果
            return account;
        } catch (Exception e) {
            //回滚事务
            transationManager.RollBackTransation();
            throw new RuntimeException();
        } finally {
            //释放连接
            transationManager.releaseTransation();
        }
    }

    public void saveAccount(Account account) {
        try {
            //开启事务
            transationManager.beginTransation();
            //执行操作
            accountDao.saveAccount(account);
            //提交事务
            transationManager.CommitTransation();
        } catch (Exception e) {
            //回滚事务
            transationManager.RollBackTransation();
            throw new RuntimeException();
        } finally {
            //释放连接
            transationManager.releaseTransation();
        }
    }

    public void updateAccount(Account account) {
        try {
            //开启事务
            transationManager.beginTransation();
            //执行操作
            accountDao.saveAccount(account);
            //提交事务
            transationManager.CommitTransation();
        } catch (Exception e) {
            //回滚事务
            transationManager.RollBackTransation();
            throw new RuntimeException();
        } finally {
            //释放连接
            transationManager.releaseTransation();
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            //开启事务
            transationManager.beginTransation();
            //执行操作
            accountDao.deleteAccount(accountId);
            //提交事务
            transationManager.CommitTransation();
        } catch (Exception e) {
            //回滚事务
            transationManager.RollBackTransation();
            throw new RuntimeException();
        } finally {
            //释放连接
            transationManager.releaseTransation();
        }
    }

    /**
     * 1.根据名称查询转出账户
     * 2.根据名称查询转入账户
     * 3.转出账户减钱
     * 4.转入账户加钱
     * 5.更新转出账户
     * 6.更新转入账户
     *
     * @param SourceName
     * @param targetName
     * @param moeny
     */
    @Override
    public void transfer(String SourceName, String targetName, Float moeny) {
        try {
            //开启事务
            transationManager.beginTransation();
            //2执行操作
            //2.1 据名称查询转出账户
            Account payUser = accountDao.findAccountbyname(SourceName);
            //2.2 根据名称查询转入账户
            Account recvUser = accountDao.findAccountbyname(targetName);
            //2.3 转出账户减钱
            payUser.setMoney(payUser.getMoney() - moeny);
            //2.4 转入账户加钱
            recvUser.setMoney(recvUser.getMoney() + moeny);
            //更新转出账户
            accountDao.updateAccount(payUser);
            int i = 1 / 0;
            //更新转入账户
            accountDao.updateAccount(recvUser);
            //提交事务
            transationManager.CommitTransation();
        } catch (Exception e) {
            //回滚事务
            transationManager.RollBackTransation();
            throw new RuntimeException();
        } finally {
            //释放连接
            transationManager.releaseTransation();
        }
    }
}

写好了以后,代码变得很臃肿,这是因为代码还未完成.
我们之前讲到,QueryRunner对象每次都会创建新的,并从数据源中拿出一个连接


QueryRunner

如果dao中执行方法的同时,给QueryRunner注入了Connection之后 它就会从连接里取一个,而现在我们并不希望它从数据源里取新的连接,所以我们需要改动一下,把注入的数据源拿走.


删除注入数据源

当我们不提供Connection对象时,dao里面的操作将没有Connection,这样显然是不行的,在Dao里加入ConnectionUtils,让dao拥有当前线程上的连接:在Dao里添加ConnectionUtils并生成set方法,让Spring为我们注入ConnectionUtils,接下来还要为query的方法的第一个参数添加当前线程上的连接的形参,dao里每个方法都要这样改造
dao里的改造

一开始不太明白这么做的原因,也想不到为什么要添加连接对象的参数,点进去看到是可以添加连接对象的和sql的构造函数的

改造完成后dao的代码

package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.util.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

public class AccountDaoImpl implements IAccountDao {
    private QueryRunner queryRunner;
    private ConnectionUtils connectionUtils;

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

    public void setQueryRunner(org.apache.commons.dbutils.QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    public List<Account> findAllAccount() {

        try {
            return queryRunner.query(connectionUtils.getConnection(),"select * from account", new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public Account findAccountbyId(Integer accountId) {
        try {
            return queryRunner.query(connectionUtils.getConnection(),"select * from account where id = ? ", new BeanHandler<Account>(Account.class), accountId);
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void saveAccount(Account account) {
        try {
            queryRunner.update(connectionUtils.getConnection(),"insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void updateAccount(Account account) {
        try {
            // queryRunner.update("update  account set name = ?,money = ? where id = ?",account.getId(),account.getName(),account.getMoney());
            queryRunner.update(connectionUtils.getConnection(),"update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            queryRunner.update(connectionUtils.getConnection(),"delete from account where id = ? ", accountId);
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 根据名称查找账户
     * 如果有唯一的一个结果就返回,如果没有就返回null
     * 如果结果集超过一个就抛异常
     *
     * @param
     * @return
     */
    @Override
    public Account findAccountbyname(String accountName) {
        try {
            List<Account> accountList = queryRunner.query(connectionUtils.getConnection(),"select * from account where name= ?", new BeanListHandler<Account>(Account.class), accountName);
            if (accountList == null || accountList.size() == 0) {
                return null;
            }
            if (accountList.size() > 1) {
                throw new RuntimeException("结果集不唯一");
            }
            return accountList.get(0);
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

当我把这些方法都补全后,我们的操作又变正常了,也就是我们的dao在执行时有了Connection的支持,并且QueryRunner在配置过程中没有注入数据源,从而实现了这个功能不会从数据源里再去取连接了,同时我们也通过工具类的方式,让当前线程与连接绑定,并编写了事务管理类
接下来,把新建的依赖,在bean.xml中配置好

配置ConnectionUtils工具类

    <!--配置ConnectionUtils-->
    <bean id="conectionUtils" class="com.itheima.util.ConnectionUtils"></bean>

接下来我们看ConnectionUtils需要什么内容,需要dataSource,所以我们为dataSource生成Set方法


ConnectionUtils

然后在bean.xm中配置注入


在xml中

在xml中配置Dao的依赖

同样的,在Service和Dao,以及TransationManager中,看他们需要什么,添加Set方法,然后在bean.xml中配置.
在Service中添加需要注入的set方法

在xml中配置注入

在TransationManerger中添加set方法

在xml中配置依赖关系

完整的bean.xml中的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置Service-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <!--注入事务控制-->
        <property name="transationManager" ref="transationManager"></property>
    </bean>
    <!--配置Dao-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="queryRunner" ref="runner"></property>
        <property name="connectionUtils" ref="conectionUtils"></property>
    </bean>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
    </bean>
    <!--注入数据源-->
    <!-- <constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
    <!--配置数据源-->
    <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/eesy"></property>
        <property name="user" value="root"></property>
        <property name="password" value="password"></property>
    </bean>
    <!--配置ConnectionUtils-->
    <bean id="conectionUtils" class="com.itheima.util.ConnectionUtils">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置TransationManerger-->
    <bean id="transationManager" class="com.itheima.util.TransationManager">
        <!--注入ConnectionUtils-->
        <property name="connection" ref="conectionUtils"></property>
    </bean>
</beans>

(学习这段花了很长时间,因为对配置各种依赖关系感到迷茫,老师各种信手拈来,而我总在想为什么我没考虑到这样做,依赖注入其实没理解透彻,为什么就要用set方法没想明白,为什么老师知道哪个类要注入什么,现在想通了.以前xml相关的竟然给遗忘了
为什么总是要用set方法---->因为要注入依赖--->怎样注入依赖?---注入的方式有三种1.构造函数注入 2.由set方法注入 3注解提供)

  • 复习一下依赖注入

再来复习一遍

因为遗忘xml的配置相关,通过一个bug来明白的.
当强行去我明白了每个类中需要什么,就来配置什么的时候(通过看了几遍视频去理解配置这个依赖关系的时候),我明白ConnectionUtils需要配置数据源,我有报错信息.想不通为什么错了,去搜 Cannot resolve property 这种错误有关的信息都无法解决,其实是我自己的问题,没有Set方法或者说set方法不叫ds.
<property>这个标签就是通过set方法来注入依赖关系的
image.png

测试转账并分析案例中的问题

接下来我们bean,xml中有关spring的配置已完成,通过test方法来测试,可以把数据库里面关于aaa和bbb的money字段改成1000以便观察


account

test方法的代码:

/**
 * 使用Junit单元测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans.xml")
public class AccountServiceTest {
    @Autowired
    private IAccountService accountService;

    @Test
    public void testTransfer() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        accountService.transfer("aaa", "bbb", 100f);
    }
}

当再遇到java.lang.ArithmeticException: / by zero 这个 异常时 更新转出账户和更新转入账户都被回滚了


遇到事务回滚

通过我们的努力,代码又回归正常了,我们的事务控制由持久层回到了业务层,同时,通过事务控制,转账能够正常执行了
问题随之而来,首先,配置十分繁琐,很多依赖注入乱七八糟:
Service用到事务管理器 Dao用到ConectionUtils ,事务管理器也用到了ConnectionUtils,他们之间互相联系互相依赖针对这个在04有办法解决.
现在的问题是现在Service里的代码重复代码很多,看起来也很繁重


代码中的问题

这里可以把Service复制一份做一个对比,现在使用的Service就叫AccountServiceImpl_old,原来未经事务控制的代码就叫做AccountServiceImp


这里可以把Service复制一份做一个对比

现在的代码,每个方法都在事务控制中执行语句
我们现在的代码

我们之前的代码:.直接就执行语句
之前更加简洁的代码

之前这段代码的问题是由于每次都要获取一个连接,导致事务无法控制,接下来,我们需要解决这个问题.

如何去解决?还有什么问题我们必须去改变?

类之间的依赖,有调用的依赖,还有方法间的依赖
比如我们在TransationManager里把beginTransaton方法的名字上加一个1


image.png

接下来我们现在所使用的这个Service中的调用就不能使用

AccountServiceImpl_old受到了影响

但是之前很简洁的Service是不受影响的,它没有依赖事务管理控制器
AccountServiceImp没有受到影响

我们一个Service就有这么多方法调用,如果出现方法名的改变,对我们来说,就很麻烦了。
所以我们需要使方法之间独立让它更加灵活,而不是紧密的依赖关系
(↑这段话已经在暗示使用工厂模式了,我没意识到,以为老师的意思是使用动态代理来改进代码))
我们如何改变这个类中的问题?换句话说,我们如何让原来很简洁的代码能够正常执行,并且能实现事务的支持?
(并且还要实现降低程序间方法的调用)
这里要回顾到动态代理,我把这个知识点分离出来了.

10.使用动态代理实现事务控制

通过动态代理,来实现对我们代码的改进,我们希望Service是原来那种简单明了代码.
动态代理可以使代码不变的情况下实现对事务的增强,而且我们需要降低程序间的耦合关系

我们如何来实现这个目标呢?


(老师的方法是先使用工厂模式创建Service,我自己完全没想到这点,为什么要用Bean工厂来创建Service?在最开始的时候,老师有举例过工厂模式。为什么要使用工厂模式呢?因为工厂模式可以降低方法间调用的依赖,JavaBean就是用Java语言编写可以重复使用的组件,比如Service和Dao是可以重复被使用的)

新建一个Class,放在Factory包下,叫BeanFactory,添加一个方法,用于创建Service的代理对象的的工厂 ,提供private IAccountService accountService,accountService对象,并为这个对象提供get方法.

 public IAccountService getAccountService() {
        return accountService;
    }

但是这个时候getAccountService(),不能直接这样返回.
我们accountService的值不能自己再获取,需要Spring注入的方式为我们提供Service.
getAccountService这个方法是来获取Service的代理对象的
getAccountService就不是返回accountService对象了,而是应该用Proxy.newProxyInstance();
然后就要写增强代码了.我们的代码的增强,主要是为Service提供事务的控制,所以直接把事务流程拿来用在里面就好了,开启事务-->执行语句-->提交事务--->(错误的情况下)回滚事务----->释放连接.
在这里增加一个事务控制器对象,让我们可以进行事务的调用.

    private TransationManager TransationManager;

听完这段我又跑去自己写,忽略了method.invoke,直接调用了对象方法

这是老师写的,首先定义了Object rtValue 返回值,在用rtValue 接受了 method.invoke()方法后的结果

这里需要注意匿名内部类访问外部成员,外部成员要用final修饰
匿名内部类访问外部成员,外部成员要用final修饰

完整代码:

package factory;

import com.itheima.service.IAccountService;
import com.itheima.service.impl.AccountServiceImpl;
import com.itheima.util.TransationManager;

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

/**
 * 用于创建Service的代理对象的工厂
 */
public class BeanFactory {
    private IAccountService accountService;
    private TransationManager TransationManager;
    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * 获取Service的代理对象
     * @return
     */
    public IAccountService getAccountService() {
        IAccountService proxyService = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(), new InvocationHandler() {
                    /**
                     * 写上增强代码,对事务的支持.
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        try{
                            TransationManager.beginTransation();
                            rtValue =  method.invoke(accountService, args);
                            TransationManager.CommitTransation();
                        }catch (Exception e){
                            TransationManager.RollBackTransation();
                        }finally {
                            TransationManager.releaseTransation();
                        }
                        return rtValue;
                    }
                });
        return proxyService;
    }
}

我们把这个写完,代理对象就创建完了,并加上了事务的控制.这个时候执行被被代理对象,accountService的任何方法都会经过代理方法.
现在,accountService不再需要transationManager了,都交给代理控制了.事务控制和业务层里的方法就分离出来了

接下来我们要继续配置下xml,为我们刚刚写的代码添加依赖.
先配置beanFactory,注入普通的没有经过代理的accountService和事务管理器

   <!--配置BeanFactory-->
    <bean id="beanFactory" class="factory.BeanFactory">
        <!--注入Service-->
        <property name="accountService" ref="accountService"></property>
        <!--注入-transationManager-->
        <property name="transationManager" ref="transationManager"></property>
    </bean>

接下来要配置代理的Service对象
这个proxyAccountService和普通的AcoountService是有区别的,之前的AcoountService是没有事务支持的,而proxyAccountService是有事务支持的.所以不能再配置普通的AccountServiceImpl
这里用了创建对象的三种办法之一的实例工厂创建了proxyService

  <!--配置代理的Service-->
    <bean id="proxyService" factory-bean="beanFactory" factory-method="getAccountService">

这个时候,IOC容器里就有两种同类型的accountService,他们两个都实现了IAccountService接口,一个是通过动态代理实现的,一个本身就是它的实现类


IOC容器里就有两种同类型的accountService

这样的话,我们在测试类中,就不能再用Autowired了,因为有两个同类型的,它无法识别我们究竟需要哪个Service,这个时候搭配 @Qualifier注解使用.
测试类的代码:

package test;

import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.util.TransationManager;
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;


/**
 * 使用Junit单元测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans.xml")
public class AccountServiceTest {
    @Autowired
    @Qualifier("proxyService")
    private IAccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer("aaa", "bbb", 100f);
}

此时把aaa和bbb的账户余额变为1000以便观察

测试结果:


可以看到有报错信息,但账户余额不再改变,已被事务控制回滚

再屏蔽掉Service里的错误代码


成功扣款

动态代理帮我实现了代码的增强,我们让重复的代码消失了,让代码间的依赖也解除了,但是配置就更繁琐了.
这个问题会由Aop为我们解决


老师还没讲之前我写了这么一段,思路是确定代理对象为Service,对它进行动态代理,增强代码为事务的控制,但是执行以后什么也没改变,跟老师思路也不太一样,写在这里当个错误记录.

package factory;

import com.itheima.service.IAccountService;
import com.itheima.service.impl.AccountServiceImpl;
import com.itheima.util.TransationManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

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

/**
 * 用于创建Service的代理对象的工厂
 */
public class BeanFactory {
    private IAccountService accountService;

    private  TransationManager TransationManager;

    public void setTransationManager(com.itheima.util.TransationManager transationManager) {
        TransationManager = transationManager;
    }

    public  final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * 获取Service的代理对象
     * @return
     */
    @Qualifier("proxyService")
    public IAccountService getAccountService() {
        IAccountService proxyService = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(), new InvocationHandler() {
                    /**
                     * 写上增强代码,对事务的支持.
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        try{
                            TransationManager.beginTransation();
                            rtValue =  method.invoke(accountService, args);
                            TransationManager.CommitTransation();
                        }catch (Exception e){
                            TransationManager.RollBackTransation();
                        }finally {
                            TransationManager.releaseTransation();
                        }
                        return rtValue;
                    }
                });
        return proxyService;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容