• java——事务


    目录

    事务

    1 service层控制事务

    2 service层控制事务失败的原因

    3 解决方案一:传递Connection

    4 解决方案二:ThreadLocal

    事务的封装

    1 问题描述

    2 完善工具类

     3 AccountServiceImpl类代码修改


    事务

    JDBC中,获得Connection对象来处理事务--提交或回滚事务--关闭连接。其事务策略是:

    - connection.setAutoCommit(false):true等价于1,false等于0
    - connection.commit():手动提交事务
    - connection.rollback():手动回滚事务

    1 service层控制事务


    package com.cxyzxc.examples07;

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

    public class AccountServiceImpl {

        /**
         * 转账业务
         * 
         * @param fromNo
         *            转账人账号
         * @param password
         *            转账人账号密码
         * @param toNo
         *            收款人账号
         * @param money
         *            转账金额
         */
        public String transfer(String fromNo, String password, String toNo,
                double money) {
            String result = "转账失败";
            AccountDaoImpl accountDaoImpl = new AccountDaoImpl();

            // 创建一个连接对象
            Connection connection = null;

            try {
                // 获取连接对象
                connection = DBUtils.getConnection();
                // 开启事务,关闭事务的自动提交,改为手动提交
                connection.setAutoCommit(false);
                // 1.验证fromNo账号是否存在
                Account fromAccount = accountDaoImpl.select(fromNo);
                if (fromAccount == null) {
                    throw new RuntimeException("卡号不存在");
                }

                // 2.验证fromNo的密码是否正确
                if (!fromAccount.getPassword().equals(password)) {
                    throw new RuntimeException("密码错误");
                }

                // 3.验证余额是否充足
                if (fromAccount.getBalance() < money) {
                    throw new RuntimeException("余额不足");
                }

                // 4.验证toNo账号是否存在
                Account toAccount = accountDaoImpl.select(toNo);
                if (toAccount == null) {
                    throw new RuntimeException("对方卡号不存在");
                }
                // 5.减少fromNo账号的余额
                fromAccount.setBalance(fromAccount.getBalance() - money);
                accountDaoImpl.update(fromAccount);

                // 程序出现异常
                int num = 10 / 0;

                // 6.增加toNo账号的余额
                toAccount.setBalance(toAccount.getBalance() + money);
                accountDaoImpl.update(toAccount);
                // 代码执行到这里,说明转账成功,提交事务
                connection.commit();
                result = "转账成功";
                return result;
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    // 出现异常,回滚整个事务
                    System.out.println("出现异常,回滚整个事务,转账失败");
                    connection.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally{
                DBUtils.closeAll(connection, null, null);
            }
            return result;
        }
    }
    ```

    2 service层控制事务失败的原因

    执行这个代码,观察account表中的数据发现,当程序出现异常,转账账号余额减少了,但是收款账户余额没有增加,事务控制失败了。失败的原因是:

    - AccountServiceImpl类中的connection连接对象与AccountDaoImpl类中给各个方法里的connection连接对象是不同的connection连接对象。
    - AccountServiceImpl类中的connection连接对象控制事务,只能控制AccountServiceImpl类中的事务,不能控制该类之外的类里的事务

    3 解决方案一:传递Connection

    为了解决AccountServiceImpl类中的connection连接对象与AccountDaoImpl类中给各个方法里的connection连接对象是不同步的问题,可以将Connection对象通过service传递给AccountDaoImpl类中的各个方法

    3.1 AccountDaoImpl类代码

    AccountDaoImpl类中的每个方法参数列表里都要添加一个Connection类型的参数
    package com.cxyzxc.examples08;

    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;

    public class AccountDaoImpl {

        // 新增:插入一个Account对象到数据库中
        public int insert(Account account,Connection connection) {
            PreparedStatement preparedStatement = null;

            String sql = "insert into account values(?,?,?,?)";

            try {
                preparedStatement = connection.prepareStatement(sql);

                // 绑定参数
                preparedStatement.setString(1, account.getCardNo());
                preparedStatement.setString(2, account.getPassword());
                preparedStatement.setString(3, account.getName());
                preparedStatement.setDouble(4, account.getBalance());

                // 执行SQL
                int result = preparedStatement.executeUpdate();
                return result;
            } catch (SQLException e) {
                e.printStackTrace();
            } 
            return 0;
        }

        // 删除:根据卡号,删除账号
        public int delete(String cardNo,Connection connection) {
            PreparedStatement preparedStatement = null;

            String sql = "delete from account where cardNo = ?;";
            try {
                preparedStatement = connection.prepareStatement(sql);
                // 绑定参数
                preparedStatement.setString(1, cardNo);
                // 执行SQL
                int result = preparedStatement.executeUpdate();
                return result;
            } catch (SQLException e) {
                e.printStackTrace();
            } 
            return 0;
        }

        // 修改
        public int update(Account account,Connection connection) {
            PreparedStatement preparedStatement = null;

            String sql = "update account set password = ?,name = ?,balance = ? where cardNo=?;";

            try {
                preparedStatement = connection.prepareStatement(sql);

                // 绑定参数
                preparedStatement.setString(1, account.getPassword());
                preparedStatement.setString(2, account.getName());
                preparedStatement.setDouble(3, account.getBalance());
                preparedStatement.setString(4, account.getCardNo());

                // 执行SQL
                int result = preparedStatement.executeUpdate();
                return result;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return 0;
        }

        // 查询单个
        public Account select(String cardNo,Connection connection) {
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            Account account = null;


            String sql = "select * from account where cardNo = ?";

            try {
                preparedStatement = connection.prepareStatement(sql);

                // 绑定参数
                preparedStatement.setString(1, cardNo);

                // 执行SQL
                resultSet = preparedStatement.executeQuery();

                if (resultSet.next()) {
                    String cardNumber = resultSet.getString("cardNo");
                    String password = resultSet.getString("password");
                    String name = resultSet.getString("name");
                    double balance = resultSet.getDouble("balance");

                    account = new Account(cardNumber, password, name, balance);
                }

                return account;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }

        // 查询所有
        public List selectAll(Connection connection) {
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            Account account = null;
            List accountList = new ArrayList();

            String sql = "select * from account;";

            try {
                preparedStatement = connection.prepareStatement(sql);

                // 执行SQL
                resultSet = preparedStatement.executeQuery();

                while (resultSet.next()) {
                    String cardNumber = resultSet.getString("cardNo");
                    String password = resultSet.getString("password");
                    String name = resultSet.getString("name");
                    double balance = resultSet.getDouble("balance");

                    account = new Account(cardNumber, password, name, balance);
                    accountList.add(account);
                }
                return accountList;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
     

    3.2 AccountServiceImpl类代码


    package com.cxyzxc.examples08;

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

    public class AccountServiceImpl {

        /**
         * 转账业务
         * 
         * @param fromNo
         *            转账人账号
         * @param password
         *            转账人账号密码
         * @param toNo
         *            收款人账号
         * @param money
         *            转账金额
         */
        public String transfer(String fromNo, String password, String toNo,
                double money) {
            String result = "转账失败";
            AccountDaoImpl accountDaoImpl = new AccountDaoImpl();

            // 创建一个连接对象
            Connection connection = null;

            try {
                // 获取连接对象
                connection = DBUtils.getConnection();
                // 开启事务,关闭事务的自动提交,改为手动提交
                connection.setAutoCommit(false);
                // 1.验证fromNo账号是否存在
                Account fromAccount = accountDaoImpl.select(fromNo,connection);
                if (fromAccount == null) {
                    throw new RuntimeException("卡号不存在");
                }

                // 2.验证fromNo的密码是否正确
                if (!fromAccount.getPassword().equals(password)) {
                    throw new RuntimeException("密码错误");
                }

                // 3.验证余额是否充足
                if (fromAccount.getBalance() < money) {
                    throw new RuntimeException("余额不足");
                }

                // 4.验证toNo账号是否存在
                Account toAccount = accountDaoImpl.select(toNo,connection);
                if (toAccount == null) {
                    throw new RuntimeException("对方卡号不存在");
                }
                // 5.减少fromNo账号的余额
                fromAccount.setBalance(fromAccount.getBalance() - money);
                accountDaoImpl.update(fromAccount,connection);

                // 程序出现异常
                int num = 10 / 0;

                // 6.增加toNo账号的余额
                toAccount.setBalance(toAccount.getBalance() + money);
                accountDaoImpl.update(toAccount,connection);
                // 代码执行到这里,说明转账成功,提交事务
                connection.commit();
                result = "转账成功";
                return result;
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    // 出现异常,回滚整个事务
                    System.out.println("出现异常,回滚整个事务,转账失败");
                    connection.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }finally{
                DBUtils.closeAll(connection, null, null);
            }
            return result;
        }
    }
     

    3.3 测试

    测试发现,这种方法可以解决service层控制事务失败的问题。

    3.4 解决方案的弊端

    (1)如果使用传递Connection,更容易造成接口污染(BadSmell)。

    (2)定义接口是为了更容易更换实现,而将Connection定义在接口中(XxxDao接口,XXXDaoImpl实现XxxDao接口)中,会造成污染当前接口。因为在当前代码中连接对象叫Connection,而在其它数据库连接框架中,连接对象不叫Connection(Mybatis框架中数据库连接对象叫SqlSession,Hibernate框架中数据库连接对象叫session),这时候,你需要重新定义接口,重新传递连接对象。

    4 解决方案二:ThreadLocal

    (1)在整个线程中(单线程),存储一个共享值(Connection对象)。

    (2)线程类中拥有一个类似Map的属性(),以键值对的结构存储数据。

    4.1 ThreadLocal应用

    一个线程中所有的操作共享一个ThreadLocal,ThreadLocal里存储的是Connection连接对象,在整个操作流程中任何一个操作环节都可以设置或者获取值。

    ![](img/13ThreadLocal核心流程.png)

    4.2 ThreadLocal代码实现

    在DBUtils类中,将当前Connection对象添加到ThreadLocal中。其它类代码保持不变。

    ```
    package com.cxyzxc.examples09;

    import java.sql.*;

    public class DBUtils {
        // 声明一个ThreadLocal对象用来存储数据库连接对象
        private static ThreadLocal threadLocal = new ThreadLocal();

        static {// 类加载,执行一次!
            try {
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        // 1.获取连接
        public static Connection getConnection() {
            // 将当前线程中绑定的Connection对象赋值给connection变量
            Connection connection = threadLocal.get();
            try {
                // 如果连接对象为null,则创建一个连接对象
                if (connection == null) {
                    connection = DriverManager.getConnection(
                            "jdbc:mysql://localhost:3306/java", "root",
                            "123456");
                    // 将创建的连接对象存储到当前线程中共享
                    threadLocal.set(connection);
                }

            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }

        // 2.释放资源
        public static void closeAll(Connection connection, Statement statement,
                ResultSet resultSet) {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (statement != null) {
                    statement.close();
                }
                if (connection != null) {
                    connection.close();
                    // 将connection从threadLocal中移除
                    threadLocal.remove();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    ```

    事务的封装

    1 问题描述

    (1)XXXDaoImpl类是专门用来操作数据表的,在这个类中只存在对数据表增删改查的方法,没有其它的内容。这是我们需要的。但是在XXXServiceImpl类中,既有业务需求,还有获取数据库连接对象以及释放资源的代码,在XXXServiceImpl类中,应该只有业务逻辑需求,除此,没有其它操作代码,这才是我们需要的。

    (2)因此我们需要将对事务的开启、提交、回滚都封装到DBUtils工具类中。业务层调用DBUtils类中的与事务相关的代码即可。

    2 完善工具类


    package com.cxyzxc.examples10;

    import java.sql.*;

    public class DBUtils {
        // 声明一个ThreadLocal对象用来存储数据库连接对象
        private static ThreadLocal threadLocal = new ThreadLocal();

        static {// 类加载,执行一次!
            try {
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        // 1.获取连接
        public static Connection getConnection() {
            // 将当前线程中绑定的Connection对象赋值给connection变量
            Connection connection = threadLocal.get();
            try {
                // 如果连接对象为null,则创建一个连接对象
                if (connection == null) {
                    connection = DriverManager.getConnection(
                            "jdbc:mysql://localhost:3306/java", "root",
                            "123456");
                    // 将创建的连接对象存储到当前线程中共享
                    threadLocal.set(connection);
                }

            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }

        // 2.释放资源
        public static void closeAll(Connection connection, Statement statement,
                ResultSet resultSet) {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (statement != null) {
                    statement.close();
                }
                if (connection != null) {
                    connection.close();
                    // 将connection从threadLocal中移除
                    threadLocal.remove();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        // 3、开启事务
        public static void startTransaction() {
            Connection connection = null;
            try {
                connection = getConnection();
                connection.setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        // 4、提交事务
        public static void commitTransaction() {
            Connection connection = getConnection();
            try {
                connection.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBUtils.closeAll(connection, null, null);
            }
        }

        // 5、回滚事务
        public static void rollbackTransaction() {
            Connection connection = getConnection();
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBUtils.closeAll(connection, null, null);
            }
        }
    }
    ```

     3 AccountServiceImpl类代码修改


    package com.cxyzxc.examples10;

    public class AccountServiceImpl {

        /**
         * 转账业务
         * 
         * @param fromNo
         *            转账人账号
         * @param password
         *            转账人账号密码
         * @param toNo
         *            收款人账号
         * @param money
         *            转账金额
         */
        public String transfer(String fromNo, String password, String toNo,
                double money) {
            String result = "转账失败";
            AccountDaoImpl accountDaoImpl = new AccountDaoImpl();

            try {

                // 开启事务,关闭事务的自动提交,改为手动提交
                DBUtils.startTransaction();
                // 1.验证fromNo账号是否存在
                Account fromAccount = accountDaoImpl.select(fromNo);
                if (fromAccount == null) {
                    throw new RuntimeException("卡号不存在");
                }

                // 2.验证fromNo的密码是否正确
                if (!fromAccount.getPassword().equals(password)) {
                    throw new RuntimeException("密码错误");
                }

                // 3.验证余额是否充足
                if (fromAccount.getBalance() < money) {
                    throw new RuntimeException("余额不足");
                }

                // 4.验证toNo账号是否存在
                Account toAccount = accountDaoImpl.select(toNo);
                if (toAccount == null) {
                    throw new RuntimeException("对方卡号不存在");
                }
                // 5.减少fromNo账号的余额
                fromAccount.setBalance(fromAccount.getBalance() - money);
                accountDaoImpl.update(fromAccount);

                // 程序出现异常
                @SuppressWarnings("unused")
                int num = 10 / 0;

                // 6.增加toNo账号的余额
                toAccount.setBalance(toAccount.getBalance() + money);
                accountDaoImpl.update(toAccount);
                // 代码执行到这里,说明转账成功,提交事务
                DBUtils.commitTransaction();
                result = "转账成功";
                return result;
            } catch (Exception e) {
                e.printStackTrace();
                // 出现异常,回滚整个事务
                System.out.println("出现异常,回滚整个事务,转账失败");
                DBUtils.rollbackTransaction();
            }
            return result;
        }
    }
     

  • 相关阅读:
    管理类联考——英语二——记忆篇——按题型记忆必要单词
    技术管理 - 学习/实践
    springboot 整合 nacos 作为注册中心
    毕设 仓库管理系统
    Jenkins简介及安装配置详解:开启持续集成之旅
    直播APP源码搭建:核心的服务器系统
    虚拟机上为AzureDevOps Server 创建用户
    Golang安装和配置指南:从零开始的高效开发之旅
    谷歌浏览器无法翻译已解决
    STL应用 —— 常用函数
  • 原文地址:https://blog.csdn.net/m0_72960906/article/details/127648914