• 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 没有变化,转账正常执行。

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

  • 相关阅读:
    JavaScript的由来, 浏览器的20年
    WEB界面onload前的加载流程❤❤
    HTML5文件系统API和资料整理
    No enclosing instance of type is accessible. Must qualify the allocation with an enclosing instance of type LeadRestControllerTest (e.g. x.new A() where x is an instance of ).
    Git Gerrit Code Review
    Nginx Configuring HTTPS servers
    自签名证书 nginx tomcat
    centos yum install nginx
    局域网 服务器 https
    分布式重复提交
  • 原文地址:https://www.cnblogs.com/zuiren/p/11437528.html
Copyright © 2020-2023  润新知