【JAVA高级】——吃透JDBC中的事务及事务的封装
创始人
2024-04-02 22:05:45

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

文章目录

  • 💖 事务
    • ✨ service层控制事务
    • ✨ service层控制事务失败的原因
    • ✨ 解决方案一:传递Connection
      • 💫 AccountDaoImpl类代码
      • 💫 AccountServiceImpl类代码
      • 💫 测试
      • 💫 解决方案的弊端
    • ✨ 解决方案二:ThreadLocal
      • 💫 ThreadLocal应用
      • 💫 ThreadLocal代码实现
  • 💖 事务的封装
    • ✨ 问题描述
    • ✨ 完善工具类
    • ✨ AccountServiceImpl类代码修改
  • 💖 投票传送门

在这里插入图片描述

💖 事务

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

✨ 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;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());// 执行SQLint 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);// 执行SQLint 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());// 执行SQLint 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);// 执行SQLresultSet = 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);// 执行SQLresultSet = 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;}
}

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

💫 测试

测试发现,这种方法可以解决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.*;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/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)XXXDaoImpl类是专门用来操作数据表的,在这个类中只存在对数据表增删改查的方法,没有其它的内容。这是我们需要的。但是在XXXServiceImpl类中,既有业务需求,还有获取数据库连接对象以及释放资源的代码,在XXXServiceImpl类中,应该只有业务逻辑需求,除此,没有其它操作代码,这才是我们需要的。

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

✨ 完善工具类

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

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

💖 投票传送门

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...