0


【JAVA高级】——吃透JDBC中的事务及事务的封装

✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。
🍎个人主页:乐趣国学的博客
🍊个人信条:不迁怒,不贰过。小知识,大智慧。
💞当前专栏:JAVA开发者成长之路
✨特色专栏:国学周更-心性养成之路
🥭本文内容:【JAVA高级】——吃透JDBC中的事务及事务的封装
更多内容点击👇
【JAVA高级】——初识JDBC中DAO数据访问对象

文章目录

在这里插入图片描述

💖 事务

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

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

✨ service层控制事务

package com.cxyzxc.examples07;import java.sql.Connection;import java.sql.SQLException;publicclassAccountServiceImpl{/**
     * 转账业务
     * 
     * @param fromNo
     *            转账人账号
     * @param password
     *            转账人账号密码
     * @param toNo
     *            收款人账号
     * @param money
     *            转账金额
     */public String transfer(String fromNo, String password, String toNo,double money){
        String result ="转账失败";
        AccountDaoImpl accountDaoImpl =newAccountDaoImpl();// 创建一个连接对象
        Connection connection = null;try{// 获取连接对象
            connection = DBUtils.getConnection();// 开启事务,关闭事务的自动提交,改为手动提交
            connection.setAutoCommit(false);// 1.验证fromNo账号是否存在
            Account fromAccount = accountDaoImpl.select(fromNo);if(fromAccount == null){thrownewRuntimeException("卡号不存在");}// 2.验证fromNo的密码是否正确if(!fromAccount.getPassword().equals(password)){thrownewRuntimeException("密码错误");}// 3.验证余额是否充足if(fromAccount.getBalance()< money){thrownewRuntimeException("余额不足");}// 4.验证toNo账号是否存在
            Account toAccount = accountDaoImpl.select(toNo);if(toAccount == null){thrownewRuntimeException("对方卡号不存在");}// 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;}}

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

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

  • AccountServiceImpl类中的connection连接对象与AccountDaoImpl类中给各个方法里的connection连接对象是不同的connection连接对象。
  • AccountServiceImpl类中的connection连接对象控制事务,只能控制AccountServiceImpl类中的事务,不能控制该类之外的类里的事务在这里插入图片描述

✨ 解决方案一:传递Connection

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

💫 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;publicclassAccountDaoImpl{// 新增:插入一个Account对象到数据库中publicintinsert(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());// 执行SQLint result = preparedStatement.executeUpdate();return result;}catch(SQLException e){
            e.printStackTrace();}return0;}// 删除:根据卡号,删除账号publicintdelete(String cardNo,Connection connection){
        PreparedStatement preparedStatement = null;

        String sql ="delete from account where cardNo = ?;";try{
            preparedStatement = connection.prepareStatement(sql);// 绑定参数
            preparedStatement.setString(1, cardNo);// 执行SQLint result = preparedStatement.executeUpdate();return result;}catch(SQLException e){
            e.printStackTrace();}return0;}// 修改publicintupdate(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());// 执行SQLint result = preparedStatement.executeUpdate();return result;}catch(SQLException e){
            e.printStackTrace();}return0;}// 查询单个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 =newAccount(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 =newArrayList<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 =newAccount(cardNumber, password, name, balance);
                accountList.add(account);}return accountList;}catch(SQLException e){
            e.printStackTrace();}return null;}}

💫 AccountServiceImpl类代码

package com.cxyzxc.examples08;import java.sql.Connection;import java.sql.SQLException;publicclassAccountServiceImpl{/**
     * 转账业务
     * 
     * @param fromNo
     *            转账人账号
     * @param password
     *            转账人账号密码
     * @param toNo
     *            收款人账号
     * @param money
     *            转账金额
     */public String transfer(String fromNo, String password, String toNo,double money){
        String result ="转账失败";
        AccountDaoImpl accountDaoImpl =newAccountDaoImpl();// 创建一个连接对象
        Connection connection = null;try{// 获取连接对象
            connection = DBUtils.getConnection();// 开启事务,关闭事务的自动提交,改为手动提交
            connection.setAutoCommit(false);// 1.验证fromNo账号是否存在
            Account fromAccount = accountDaoImpl.select(fromNo,connection);if(fromAccount == null){thrownewRuntimeException("卡号不存在");}// 2.验证fromNo的密码是否正确if(!fromAccount.getPassword().equals(password)){thrownewRuntimeException("密码错误");}// 3.验证余额是否充足if(fromAccount.getBalance()< money){thrownewRuntimeException("余额不足");}// 4.验证toNo账号是否存在
            Account toAccount = accountDaoImpl.select(toNo,connection);if(toAccount == null){thrownewRuntimeException("对方卡号不存在");}// 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;}}

💫 测试

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

💫 解决方案的弊端

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

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

✨ 解决方案二:ThreadLocal

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

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

💫 ThreadLocal应用

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

在这里插入图片描述

💫 ThreadLocal代码实现

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

package com.cxyzxc.examples09;import java.sql.*;publicclassDBUtils{// 声明一个ThreadLocal<Connection>对象用来存储数据库连接对象privatestatic ThreadLocal<Connection> threadLocal =newThreadLocal<Connection>();static{// 类加载,执行一次!try{
            Class.forName("com.mysql.jdbc.Driver");}catch(ClassNotFoundException e){
            e.printStackTrace();}}// 1.获取连接publicstatic 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.释放资源publicstaticvoidcloseAll(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)XXXDaoImpl类是专门用来操作数据表的,在这个类中只存在对数据表增删改查的方法,没有其它的内容。这是我们需要的。但是在XXXServiceImpl类中,既有业务需求,还有获取数据库连接对象以及释放资源的代码,在XXXServiceImpl类中,应该只有业务逻辑需求,除此,没有其它操作代码,这才是我们需要的。

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

✨ 完善工具类

package com.cxyzxc.examples10;import java.sql.*;publicclassDBUtils{// 声明一个ThreadLocal<Connection>对象用来存储数据库连接对象privatestatic ThreadLocal<Connection> threadLocal =newThreadLocal<Connection>();static{// 类加载,执行一次!try{
            Class.forName("com.mysql.jdbc.Driver");}catch(ClassNotFoundException e){
            e.printStackTrace();}}// 1.获取连接publicstatic 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.释放资源publicstaticvoidcloseAll(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、开启事务publicstaticvoidstartTransaction(){
        Connection connection = null;try{
            connection =getConnection();
            connection.setAutoCommit(false);}catch(SQLException e){
            e.printStackTrace();}}// 4、提交事务publicstaticvoidcommitTransaction(){
        Connection connection =getConnection();try{
            connection.commit();}catch(SQLException e){
            e.printStackTrace();}finally{
            DBUtils.closeAll(connection, null, null);}}// 5、回滚事务publicstaticvoidrollbackTransaction(){
        Connection connection =getConnection();try{
            connection.rollback();}catch(SQLException e){
            e.printStackTrace();}finally{
            DBUtils.closeAll(connection, null, null);}}}

✨ AccountServiceImpl类代码修改

package com.cxyzxc.examples10;publicclassAccountServiceImpl{/**
     * 转账业务
     * 
     * @param fromNo
     *            转账人账号
     * @param password
     *            转账人账号密码
     * @param toNo
     *            收款人账号
     * @param money
     *            转账金额
     */public String transfer(String fromNo, String password, String toNo,double money){
        String result ="转账失败";
        AccountDaoImpl accountDaoImpl =newAccountDaoImpl();try{// 开启事务,关闭事务的自动提交,改为手动提交
            DBUtils.startTransaction();// 1.验证fromNo账号是否存在
            Account fromAccount = accountDaoImpl.select(fromNo);if(fromAccount == null){thrownewRuntimeException("卡号不存在");}// 2.验证fromNo的密码是否正确if(!fromAccount.getPassword().equals(password)){thrownewRuntimeException("密码错误");}// 3.验证余额是否充足if(fromAccount.getBalance()< money){thrownewRuntimeException("余额不足");}// 4.验证toNo账号是否存在
            Account toAccount = accountDaoImpl.select(toNo);if(toAccount == null){thrownewRuntimeException("对方卡号不存在");}// 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;}}

💖 投票传送门

标签: java sql servlet

本文转载自: https://blog.csdn.net/hh867308122/article/details/127636114
版权归原作者 Java Fans 所有, 如有侵权,请联系我们删除。

“【JAVA高级】——吃透JDBC中的事务及事务的封装”的评论:

还没有评论