JDBC中事务回滚
首先,什么是 事务回滚 ?
理解:防止出现未知错误,导致原先要执行完全的数据只执行了一半,最终影响数据,也就是 事务是一组组合成逻辑工作单元的操作,虽然系统中可能会出错,但事务将控制和维护事务中每个操作的一致性和完整性。
事务遵循ACID原则:
原子性:要么全部完成,要么都不完成
一致性:总数不变
隔离性:多个进程互不干扰
持久性:一旦提交不可逆,即持久化到数据库
事务回滚作用
假设现在有一个业务逻辑是 张三 给 李四 转账,张三在银行A发起了转账操作(此处我们假设银行使用mysql进行数据存储),此时数据库需要完成两个操作,第一个操作是从张三的余额当中扣除对应的金额,第二个操作是给李四的账户余额中增加余额。
突然有一天,出现了一个bug,在张三转账的时候,系统扣除了张三的余额后,系统突然故障,无法正常工作,崩溃了。但是这个时候李四的余额还没增加,这个时候这笔余额就无缘无故消失了。
这就增加了问题严重性。这个时候,事务回滚就是用来防止这种情况的发生的,事务回滚在这个场景的用处简单来说就是 将张三扣除余额和李四增加余额,这两个操作绑定在一次,同时操作,这样就避免了当张三扣除余额后,李四的余额每增加这种问题的发生。
JDBC事务回滚的语法说明
基础语法:
conn.setAutoCommit(boolean)
:设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置为false,那么相当于开启了事务了;conn.setAutoCommit(false) 表示开启事务。
conn.commit()
:提交结束事务。(将所有操作在这一步一起执行)
conn.rollback()
:回滚事务。(返回到数据初始的章台)
示例代码(来自 csdn文章 )
package com.mystudy.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Affair {
public static void main(String\[\] args) {
Connection conn= null;
int a = 0,b=0;
PreparedStatement ps= null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1?useSSL=true","root","root");
//关闭数据库的自动提交
conn.setAutoCommit(false);
String sql1="UPDATE student SET stu_salary=stu_salary+1000 WHERE stu_id=1";
ps = conn.prepareStatement(sql1);
ps.executeUpdate();
int x=1/0; //此处是制造bug,使程序中断
String sql2="UPDATE student SET stu_salary=stu_salary-1000 WHERE stu_id=3";
ps = conn.prepareStatement(sql2);
ps.executeUpdate();
//业务完毕,提交事务
conn.commit();
System.out.println("执行成功");
} catch (SQLException \| ClassNotFoundException e) {
try {
//如果失败则默认回滚,
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
}
finally {
//资源释放
try {
ps.close();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
JDBC Savepoint 创建检查点
有时候一个事务可能是一组复杂的语句,因此可能想要回滚到事务中某个特殊的点。JDBC Savepoint帮我们在事务中创建检查点(checkpoint),这样就可以回滚到指定点。当事务提交或者整个事务回滚后,为事务产生的任何保存点都会自动释放并变为无效。把事务回滚到一个保存点,会使其他所有保存点自动释放并变为无效。
1.在事务中创建检查点
2.异常中捕捉检查点并回滚到检查点
savepoint = conn.setSavepoint("检查点");
//设置检查点
conn.rollback(savepoint);
//回顾到指定的检查点
示例代码(来自 csdn文章 )
import cn.hutool.core.util.StrUtil;
import java.sql.*;
public class Jdbc {
public static final String URL = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC";
public static final String USER = "root";
public static final String PASSWORD = "123456";
public static void main(String[] args) throws Exception {
//1.加载驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
//3.操作数据库,实现增删改查
String classSql = "insert into class (id, teacher_id) values (?,?)";
String userSql = "insert into users (name, age) values (?,?)";
Savepoint savepoint = null;
try {
//设置事务自动提交为手动提交
conn.setAutoCommit(false);
PreparedStatement preparedStatement1 = conn.prepareStatement(classSql);
for (int i = 20; i < 30; i++) {
preparedStatement1.setObject(1, i);
preparedStatement1.setObject(2, i);
preparedStatement1.addBatch();
}
int [] success = preparedStatement1.executeBatch();
System.out.println(success.length);
//设置回滚的点 失败只会回滚users信息
savepoint = conn.setSavepoint("检查点");
//手动制造异常
int m = 10 / 0;
PreparedStatement preparedStatement2 = conn.prepareStatement(userSql);
preparedStatement2.setObject(1, "腕豪");
preparedStatement2.setObject(2, "24");
preparedStatement2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
if (StrUtil.isEmptyIfStr(savepoint)) {
//此处判断savepoint检查点是否为空,如果是空,就回滚整个事务
//回滚所有更新sql
conn.rollback();
System.out.println("JDBC Transaction rolled back successfully");
} else {
//如果不为空,即代表设置了检查点
//回滚到指定位置
conn.rollback(savepoint);
System.out.println("JDBC Transaction rolled back successfully");
//提交检查点之前设置的事务
conn.commit();
}
} finally {
//执行完数据库操作后记得关闭数据库连接资源
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
`}`