重磅消息!!!自动化测试居然这么多好处!!!

java中的异常

  返回  

Spring--Account转账

2021/7/20 21:17:13 浏览:

Spring------Account转账

用spring框架完成Account转账

代码演示

1、先在AccountDao中定义根据账户名查找的方法

/*根据账户名查找
     * 如果有唯一的一个结果就返回,如果没有结果就返回null
     * 如果结果集超过一个就抛异常*/
    Account findByName(String accountName);

2、在AccountDaoImpl实现类中完成上面方法的实现

 public Account findByName(String accountName) {

        try{
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name=?", new BeanListHandler<Account>(Account.class), accountName);
            if (accounts == null || accounts.size() ==0) {
                return null;
            }
            if (accounts.size()>1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        }catch (Exception e){
            throw new RuntimeException();
        }

注意:

​ 这里用到了ConnectionUtils里面的getThreadConnection()方法,这个方法是用于获取当前线程上的连接。

3、AccountService中方法定义:

 /*转账操作*/
    void transfer(String sourceName,String targetName,Float money);

4、AccountServiceImpl实现类中方法的实现

public void transfer(String sourceName, String targetName, Float money) {
        try{
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            //2.1.根据名称查询转出账户
            Account acc1 = accountDao.findByName(sourceName);
            //2.2.根据名称查询转入账户
            Account acc2 = accountDao.findByName(targetName);
            //2.3.转出账户减钱
            double v1 = acc1.getMoney() - money;
            acc1.setMoney(v1);
            //2.4.转入账户加钱
            double v2 = acc2.getMoney() + money;
            acc2.setMoney(v2);
            //2.5.更新转出账户
            accountDao.updateAccount(acc1);

            int i=1/0;


            //2.6.更新转入账户
            accountDao.updateAccount(acc2);
            //3.提交事务
            txManager.commit();


        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
            e.printStackTrace();

        }finally {
            //5.释放连接
            txManager.release();
        }
}

5、测试类代码:

 /*转账*/
    @Test
    public void testTranfer(){
        //3.执行方法
       as.transfer("bbb","ccc",100f);


    }

6、连接工具类代码:

package com.itheima.utils;

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

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

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定,
 * 这样就可以避免因为多次获取连接,持久层方法都是独立的事务,导致无法出现事务控制。
 */

@Component("connectionUtils")
public class ConnectionUtils {

    private ThreadLocal<Connection> tL =new ThreadLocal<Connection>();    //!!!!别忘new对象

    @Autowired
    private DataSource dataSource;


    /*获取当前线程上的连接*/
    public Connection getThreadConnection(){
        try{
            //1.先从ThreadLocal上获取
            Connection conn=tL.get();
            //2.判断当前线程上是否有连接
            if (conn == null) {
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tL.set(conn);

            }
            //返回当前线程上的连接
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    /*把连接和线程解绑*/
    public void removeConnection(){
        tL.remove();
    }
}

管理事务代码:

package com.itheima.utils;

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

import java.sql.SQLException;

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

@Component("txManager")
public class TransactionManager {

    @Autowired
    private 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();
        }
    }


}

文字叙述

1、为什么创建连接和管理事务的两个工具类?

因为我们在进行银行转账的时候,如果中间某处出错,数据库中的值没有回滚,前面的操作提交了,但是后续的操作失败,转账账户和收款账户的钱数总额发生变化,违背了事务的一致性。

为什么会这样呢?

因为每次都获取一个连接导致无法实现事务控制,一旦某个环节出现错误,导致下面的代码无法正常执行,造成前后的不一致。

即我们每次只需持久层方法都是独立的事务,导致无法出现事务控制。

所以只要让他们只取用一个连接就可以避免这个问题。

image-20210720155840810

解决:

  • 需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象。

所以有了ConnectionUtils工具类

而TransactionManager是用于管理事务

bean.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.itheima"></context:component-scan>
    
    <!--配置runner的bean对象,它默认是单例对象,若多个dao使用时可能会造成线程干扰-->
    
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> </bean>

    <!--配置数据源的bean对象-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息,基本数据类型-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy_spring?serverTimezone=GMT&amp;useSSL=false"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

注意

这里不希望创建QueryRunner时,注入DataSource,不然,总会从连接里去,无法完成线程和事务的控制,所以这里不注入。
​ 不希望他从连接池里取连接,不提供Connection对象时,要在dao中加ConnectionUtils对象

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号