事务处理及批量更新

事务的概念和MySQL事务支持

事务:是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么不执行,要么全部执行。

事务具有四个特性:

  • 原子性:事务是应用中最小的执行单元。
  • 一致性:事务执行的结果必须使数据库从一个一致性状态转换到另一个一致性状态。
    当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该事务对数据库做的修改已经被写入数据库,此时,数据库处于不正确的状态。
    例如:A给B转账1000元,系统先在A的账户减去1000,再在B的账户加上1000,如果都执行成功,则数据库处于一致性状态,如果给A减了1000,但在给B加上1000时,出错了,没有执行成功,此时就没有处于一致性状态。
  • 隔离性:各个事务的执行互不干扰
  • 持续性:指事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存到物理数据库

数据库事务由以下语句构成:

  • 一组数据操作语句(DML);
  • 一条数据定义语句(DDL);
  • 一条数据控制语句(DCL);
注意

DDL和DCL语句最多只能有一条,因为DDL和DCL语句都会导致事务立即提交。

当事务全部执行成功后,应该提交事务。事务提交有两种方式:

  • 显式提交:使用commit
  • 自动提交:执行DCL或DDL语句,或者程序正常退出

当事务包含的操作执行失败后,应该回滚(rollback)事务,使之前所做的修改全部失效。事务回滚有两种方式:

  • 显式回滚:使用rollback
  • 自动回滚:系统错误或者强行退出

MySQL默认打开自动提交,在默认情况,用户输入一条DML语句,这条语句将会立即保存到数据库。
使用set autocommit = 0 开启显式提交。但只对本次连接有效,对其他连接到数据库的连接无效。

注意

使用commit提交只对DML语句有效,当执行DDL和DCL时,不管是否为显式提交,都会立即提交。

例子:

1
2
3
4
set autocommit = 0;
begin;
insert into choose Values('Get', 20, 11.1, 'Out', 'HaHa');
select * from choose;

结果

image-20200412102618429

接着执行

1
2
rollback;
select * from choose;

结果:

image-20200412102738691

JDBC的事务支持

JDBC连接事务支持由Conncetion提供,Connection默认打开自动提交。

调用setAutoCommit()方法关闭自动提交:

conn.setAutoCommit(false)

调用commit()方法提交事务:

conn.commit()

如果任何一条语句执行失败,用rollback回滚事务:

conn.rollback()

提示

当Connection遇到未处理的SQLException异常时,系统会非正常退出,事务会自动回滚,但是如果程序捕获了该异常,需要在异常处理块中显式回滚事务。

Connection也提供了设置中间点的方法:Savepoint setSavepoint()、Savepoint setSavepoint(String name)

通常没有必要给中间点设置名称,当回滚到指定中间点时,是根据中间点对象回滚的,使用rollback(Savepoint savepoint)回滚到指定中间点。

批量更新

使用批量更新时,多条SQL语句被作为一批操作同时收集,并同时提交。

提示

批量更新要得到底层数据库支持,可以调用DatabaseMetaData的supportBatchUpdate()方法查看底层数据库是否支持批量更新

使用批量更新需要创建一个Statement对象,用该对象的addBatch()方法收集多条SQL语句,最后调用exexuteLargeBatch()或executeBatch()执行这些语句。

实例:

1
2
3
4
5
6
7
8
Statement stmt = conn.createStatement();
//收集多条SQL语句
stmt.addBatch(sql1);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
...
//同时执行多条语句
stmt.executeLargeBatch();

执行后返回一个long[]数组,每个long值表示每条语句影响的行数,若addBatch()添加了查询语句,程序会直接出现错误。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

import java.sql.*;
import java.io.*;
import java.util.*;


public class BatchTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile) throws Exception
{
// 使用Properties类来加载属性文件
var props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void insertBatch(String[] sqls) throws Exception
{
// 加载驱动
Class.forName(driver);
try (
Connection conn = DriverManager.getConnection(url, user, pass))
{
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
// 保存当前的自动的提交模式
boolean autoCommit = conn.getAutoCommit();
// 关闭自动提交
conn.setAutoCommit(false);
try (
// 使用Connection来创建一个Statement对象
Statement stmt = conn.createStatement())
{
// 循环多次执行SQL语句
for (var sql : sqls)
{
stmt.addBatch(sql);
}
// 同时提交所有的SQL语句
stmt.executeLargeBatch();
// 提交修改
conn.commit();
// 恢复原有的自动提交模式
conn.setAutoCommit(autoCommit);
}
// 提交事务
conn.commit();
}
}
public static void main(String[] args) throws Exception
{
var tt = new TransactionTest();
tt.initParam("mysql.ini");
var sqls = new String[]{
"insert into student_table values(null, 'aaa', 1)",
"insert into student_table values(null, 'bbb', 1)",
"insert into student_table values(null, 'ccc', 1)",
};
tt.insertInTransaction(sqls);
}
}


事务处理及批量更新
https://zhaoquaner.github.io/2022/05/11/Java/JDBC/事务处理及批量更新/
更新于
2022年5月22日
许可协议