事务回滚
- 理解:防止出现未知错误,导致原先要执行完全的数据只执行了一半,最终影响数据,也就是 事务是一组组合成逻辑工作单元的操作,虽然系统中可能会出错,但事务将控制和维护事务中每个操作的一致性和完整性。
- 事务遵循ACID原则:
- 原子性:要么全部完成,要么都不完成
- 一致性:总数不变
- 隔离性:多个进程互不干扰
- 持久性:一旦提交不可逆,即持久化到数据库
- 下面在实例中体现事务回滚的作用,假设现在有个转账业务
- 且有如下的用户信息:
- 当我们想要对这个表进行如下操作:
- 对stu_id=1的用户stu_salary+1000
- 对stu_id=3的用户stu_salary-1000
正常执行的code:
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;
PreparedStatement ps= null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1?useSSL=true","root","root");
String sql1="UPDATE student SET stu_salary=stu_salary+1000 WHERE stu_id=1";
ps = conn.prepareStatement(sql1);
ps.executeUpdate();
String sql2="UPDATE student SET stu_salary=stu_salary-1000 WHERE stu_id=3";
ps = conn.prepareStatement(sql2);
ps.executeUpdate();
System.out.println("执行成功");
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
finally {
//资源释放
try {
ps.close();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
结果:
注:很成功!
第一个执行第二个未执行的code:
- 假设有未知原因这里将未知原因显式的定义成:
int x=1/0;
从而导致第一个执行,第二个尚未执行的code:
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");
String sql1="UPDATE student SET stu_salary=stu_salary+1000 WHERE stu_id=1";
ps = conn.prepareStatement(sql1);
ps.executeUpdate();
int x=1/0;
String sql2="UPDATE student SET stu_salary=stu_salary-1000 WHERE stu_id=3";
ps = conn.prepareStatement(sql2);
ps.executeUpdate();
System.out.println("执行成功");
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
finally {
//资源释放
try {
ps.close();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
结果::
注:成功执行了第一个sql语句,但是第二个由于int x=1/0;
的错误而没有执行,显然,这样是不规范的。那么就有了下面的事务回滚
- 利用事务回滚,实现事务的原子性:
两个一起执行,或者一起不执行的code:
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;
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();
}
}
}
}
结果:
- 解释:
1. 因为数据库是自动提交的(就是执行code后读取到哪执行到哪,这也是要进行事务回滚的原因),所以我们要将数据库的自动提交关闭,那么使用code:conn.setAutoCommit(false);
2. 关闭了自动提交,但总是要提交事务的,由该code:conn.commit();
实现
3. 上面两个设置了基本就可以实现原子性,这里为了防止意外,在catch语句中再次显式的用:conn.rollback();
实现事物的回滚