目录
1. 事务的理解
事务是一个包含多个步骤的业务操作,如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
在MySQL中使用事务的三条语句:
- 开启事务: START TRANSACTION; 或者 BEGIN;
- 回滚:ROLLBACK;
- 提交:COMMIT;
举个需要用到事务的例子:
这个问题,就可以通过事务来解决,把中间的代码当作处理业务的最小单元,要么全部成功,张三500,李四1500;要么全部失败,张三1000,李四1000;逻辑就变为下图:
事务提交的两种方式:
- 自动提交:一条DML(增删改)语句会自动提交一次事务;MySQL数据库中事务默认自动提交
- 手动提交:需要先开启事务,再提交(Oracle 数据库默认是手动提交事务)
修改事务的默认提交方式:
- 查看事务的默认提交方式:SELECT @@autocommit; -- 1 代表自动提交,0 代表手动提交
- 修改默认提交方式: SET @@autocommit = 0;
2. 事务的四大特性(ACID特性)
-
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
-
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
-
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
-
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3. 事务的隔离级别
多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。
存在的问题:
- 脏读:一个事务,读取到另一个事务中没有提交的数据
- 不可重复读:在同一个事务中,两次读取到的数据不一样。
- 幻读(虚读):一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。
隔离级别:
1. read uncommitted:读未提交
产生的问题:脏读、不可重复读、幻读
2. read committed:读已提交 (Oracle默认)
产生的问题:不可重复读、幻读
3. repeatable read:可重复读 (MySQL默认)
产生的问题:幻读
4. serializable:串行化
可以解决所有的问题
注意:隔离级别从小到大安全性越来越高,但是效率越来越低;
- 查询隔离级别:select @@tx_isolation;
- 设置隔离级别:set global transaction isolation level 级别字符串;
4. JDBC控制事务
使用Connection对象来管理事务
- 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务;在执行sql之前开启事务
- 提交事务:commit(); 当所有sql都执行完提交事务
- 回滚事务:rollback(); 在catch中回滚事务
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author Yuan
* @create 2020-05-31 20:55
*/
public class Test {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
//1.导入jar包
try {
//2.注册驱动,mysql5之后可以省略
// 8.0之前驱动类 --- com.mysql.jdbc.Driver
// 8.0之后驱动类 --- com.mysql.cj.jdbc.Driver
Class.forName("com.mysql.cj.jdbc.Driver");
//3.获取数据库连接对象
//Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");
//连接的是本机服务器,url可以简写
conn = DriverManager.getConnection("jdbc:mysql:///xxx?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");
//4.开启事务
conn.setAutoCommit(false);
//5.定义sql
String sql1 = "update account set balance = balance - ? where id = ?";
String sql2 = "update account set balance = balance + ? where id = ?";
//6.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//7.设置参数
pstmt1.setDouble(1, 500);
pstmt1.setInt(2, 1);
pstmt2.setDouble(1, 500);
pstmt2.setInt(2, 2);
//8.执行sql
pstmt1.executeUpdate();
//手动制造异常
//int i = 3 / 0;
pstmt2.executeUpdate();
//9.提交事务
conn.commit();
} catch (Exception e) {
//10.事务回滚
try {
if (conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
//11.释放资源
if (pstmt2 != null){
try {
pstmt2.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt1 != null){
try {
pstmt1.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
注意点:
- MySQL 8.0之前驱动类 --- com.mysql.jdbc.Driver
- MySQL 8.0之后驱动类 --- com.mysql.cj.jdbc.Driver
- jar包 --- mysql-connector-java-8.0.13.jar
- URL --- jdbc:mysql://localhost:3306/user?characterEncoding=utf-8&serverTimezone=UTC
- 只要业务出现异常,无论任何异常,就进行回滚;所以catch抓异常抓最大的Exception
- 回滚操作、释放资源前,先进行非空判断