目录

事务

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<Account> selectAll(Connection connection) {
         PreparedStatement preparedStatement = null;
         ResultSet resultSet = null;
         Account account = null;
         List<Account> accountList = new ArrayList<Account>();        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的属性(),以键值对的结构<ThreadLocal对象,值>存储数据。

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<Connection>对象用来存储数据库连接对象
     private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();    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/java22182", "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<Connection>对象用来存储数据库连接对象
     private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();    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/java22182", "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;
     }
 }