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<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/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<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/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;
    }
}
 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿是过客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值