9-分析事物问题并编写 Utils 文件

接下来安排

  1. 完善 account 案例
  2. 分析案例中问题
  3. 回顾之前讲过的一个技术:动态代理
  4. 动态代理另一种实现方式
  5. 解决案例中的问题
  6. AOP 的概念
  7. spring 中的 AOP 相关术语
  8. spring 中基于 XML 和注解的 AOP

一、创建新工程

创建新的 maven 工程,命名为 spring07

做一个转账的小操作

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring07</groupId>
    <artifactId>spring07</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.结构

3.接口与实现类添加的操作

ⅠIAccountDao

    /**
     * 根据名称查询账户
     * @param accountName 账户名称
     * @return 如果有唯一结果就返回,没有就返回 Null, 如果结果有多个,这返回错误
     */
    Account findAccountByName(String accountName);

Ⅱ AccountDaoImpl

    public void updateAccount(Account account) {
        try {
            runner.update("update account02 set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (SQLException e){
            e.printStackTrace();
        }
    }

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

Ⅲ IAccountService

    /**
     * 转账
     * @param sourceName 转出账户名称
     * @param targetName 转入账户名称
     * @param money      转账金额
     */
    void transfer(String sourceName,String targetName,Float money);

    /**
     * 更新
     * @param account
     */
    void updateAccount(Account account);

四 IAccountService

    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source=accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target=accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        //6.更新转入账户
        accountDao.updateAccount(target);
    }

Ⅴ Test

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

测试结果没有问题

Ⅵ 问题

假如 我们在AccountServiceImpl 这样操作一波,多加了个 int i=1/0;

    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source=accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target=accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        
        int i=1/0;
        //6.更新转入账户
        accountDao.updateAccount(target);
    }

运行,你会看到 source 的账户减钱了,而 target 的账户却没有价钱,这怎么办?

原因:

这是一个多例对象

解决:

要让四个操作全为同一个 connection 来实现

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

二、编写 ConnectionUtils

创建 utiles 文件夹

创建 ConnectionUtils 类

创建 TransactionManager类

1. ConnectionUtils 类

package com.utils;

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

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

/**
 * 描述:
 * 〈连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定〉
 *
 * @author zuiren
 * @create 2019/8/30
 * @since 1.0.0
 */
@Component("connectionUtils")
public class ConnectionUtils {
    private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();

    @Autowired
    private DataSource dataSource;

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

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

2. TransactionManager类

package com.utils;

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

import java.sql.SQLException;

/**
 * 描述:
 * 〈和事务相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接〉
 *
 * @author zuiren
 * @create 2019/8/30
 * @since 1.0.0
 */
@Component(value = "txManager")
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 开启事务
     */
    public void beginTransaction() {
        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();
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

细节

连接使用了连接池,

连接池的好处:把消耗时间连接获取连接的这部分放到应用加载一开始。

在 web 工程中,当我们启动tomcat加载应用时,我们创建一些连接,从而在后续项目运行阶段不在跟数据库获取链接保证了我们使用 connection 时的使用效率。

我们使用服务器,服务器也会有一个池的技术叫做线程池,它的特点是当 connection 启动时,会启动一大堆的线程放到一个容器中,接下来我们每次访问,它都是从线程池中拿出一个线程给我们使用。

这样的话线程池中的线程也跟我们连接池中的一样,所以我们最后调用

connectionUtils.getThreadConnection().close();

的方法并不是将其关闭,而是将他放回线程池中。

造理推断,线程用完了,也不是真正的关了,而是把线程还回线程池中。

所以这个线程中是绑着一个连接的,当我们把连接关闭,线程还回池中时,线程上是有连接的,只不过这个连接已经被关闭了,当我们下次再获取这个线程判断上面有没有连接时,你得到的结果一定是有,但是这个连接已经不能用了,因为它已经被 close 过了,被加载过池子里去了。

所以从这点上来说:我们应该在整个这个线程用完了之后,把这个线程和这个连接进行解绑。

在 ConnectionUtils 类中添加一方法

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

在 TransactionManager 类

    /**
     * 释放连接
     */
    public void release(){
        try {
            //还回池中
            connectionUtils.getThreadConnection().close();
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

三、编写业务层和持久层事务控制代码并配置 spring 的 ioc

1.AccountServiceImpl

package com.service.Impl;

import com.dao.IAccountDao;
import com.domain.Account;
import com.service.IAccountService;
import com.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.TreeMap;

/**
 * 描述:
 * 〈〉
 *
 * @author zuiren
 * @create 2019/8/29
 * @since 1.0.0
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    @Autowired
    private TransactionManager txManager;

    public List<Account> findAllAccount() {
        List<Account> accounts=null;
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accounts=accountDao.findAllAccount();
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return accounts;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
        }finally {
            //6.释放资源
            txManager.release();
        }
        return accounts;
    }

    public Account findAccountById(Integer accountId) {
        Account account=null;
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            account=accountDao.findAccountById(accountId);
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return account;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
        }finally {
            //6.释放资源
            txManager.release();
        }
        return account;
    }

    public void saveAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.saveAccount(account);
            //3.提交事务
            txManager.commit();
            //4.返回结果

        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
        }finally {
            //6.释放资源
            txManager.release();
        }

    }

    public void updateAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.updateAccount(account);
            //3.提交事务
            txManager.commit();
            //4.返回结果

        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
        }finally {
            //6.释放资源
            txManager.release();
        }

    }

    public void deleteAccount(Integer accountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.deleteAccount(accountId);
            //3.提交事务
            txManager.commit();
            //4.返回结果

        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
        }finally {
            //6.释放资源
            txManager.release();
        }

    }

    public void transfer(String sourceName, String targetName, Float money) {
        try {
            txManager.beginTransaction();
            //1.根据名称查询转出账户
            Account source=accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
            Account target=accountDao.findAccountByName(targetName);
            //3.转出账户减钱
            source.setMoney(source.getMoney()-money);
            //4.转入账户加钱
            target.setMoney(target.getMoney()+money);
            //5.更新转出账户
            accountDao.updateAccount(source);

            int i=1/0;
            //6.更新转入账户
            accountDao.updateAccount(target);

            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
        }finally {
            txManager.release();
        }
    }
}

2.AccountDaoImpl

这里只是在 runner 执行操作时,给它配置连接

package com.dao.Impl;

import com.dao.IAccountDao;
import com.domain.Account;
import com.utils.ConnectionUtils;
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 org.springframework.stereotype.Service;

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

/**
 * 描述:
 * 〈〉
 *
 * @author zuiren
 * @create 2019/8/29
 * @since 1.0.0
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private QueryRunner runner;

    @Autowired
    private ConnectionUtils connectionUtils;

    public List<Account> findAllAccount() {
        try {
            return runner.query(connectionUtils.getThreadConnection(),"select *from account02",new BeanListHandler<Account>(Account.class));
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public Account findAccountById(Integer accountId) {
        try {
            return runner.query(connectionUtils.getThreadConnection(),"select *from account02 where id = ?",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void saveAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"insert into account02(name,money) values(?,?)",account.getName(),account.getMoney());
        }catch (SQLException e){
            e.printStackTrace();
        }
    }

    public void updateAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"update account02 set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (SQLException e){
            e.printStackTrace();
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"delete from account02 where id=?",accountId);
        }catch (SQLException e){
            e.printStackTrace();
        }
    }


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

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return accounts.get(0);
    }
}

3.Test

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

4.结果

你会发现报错,事务回滚,aaa 的 money 没有变化,转账正常执行。

问题是现在依赖特别混乱,以后会解决

posted @ 2019-08-30 23:05  ClzSkywalker  阅读(205)  评论(0编辑  收藏  举报