✅作者简介:热爱国学的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 Fans 所有, 如有侵权,请联系我们删除。