✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。
🍎个人主页:乐趣国学的博客
🍊个人信条:不迁怒,不贰过。小知识,大智慧。
💞当前专栏:JAVA开发者成长之路
✨特色专栏:国学周更-心性养成之路
🥭本文内容:【JAVA高级】——吃透JDBC中的事务及事务的封装
更多内容点击👇
【JAVA高级】——初识JDBC中DAO数据访问对象
在JDBC中,获得Connection对象来处理事务–提交或回滚事务–关闭连接。其事务策略是
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;}
}
执行这个代码,观察account表中的数据发现,当程序出现异常,转账账号余额减少了,但是收款账户余额没有增加,事务控制失败了。失败的原因是:

为了解决AccountServiceImpl类中的connection连接对象与AccountDaoImpl类中给各个方法里的connection连接对象是不同步的问题,可以将Connection对象通过service传递给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;}
}
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),这时候,你需要重新定义接口,重新传递连接对象。

(1)在整个线程中(单线程),存储一个共享值(Connection对象)。
(2)线程类中拥有一个类似Map的属性(),以键值对的结构<ThreadLocal对象,值>存储数据。
一个线程中所有的操作共享一个ThreadLocal,ThreadLocal里存储的是Connection连接对象,在整个操作流程中任何一个操作环节都可以设置或者获取值。

在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);}}
}
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;}
}
下一篇:win11开机死机解决教程