从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

    • JavaEE 概念

    • Ant

    • Maven

    • 日志

    • Junit

    • JDBC

      • 什么是 JDBC
      • JDBC 连接、查询和更新
      • JDBC 连接字符串
      • JDBC 常用类介绍
      • PreparedStatement 是如何防止SQL注入的
      • ResultSet 结果封装为对象
      • JDBC 工具类
      • JDBC 更新
      • JDBC 事务和批量
        • 什么是事务
        • JDBC 中的事务
        • 设定事务的隔离级别
        • JDBC Batch
        • 总结
      • JDBC 连接池
      • JDBCTemplate
    • XML 和 JSON

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaEE
  • JDBC
2023-03-24
目录

JDBC 事务和批量

# 20.JDBC 事务和批量

讲讲 JDBC 中如何使用事务 ‍ ‍

# 什么是事务

数据库事务(Transaction)是由若干个 SQL 语句构成的一个操作序列。数据库系统保证在一个事务中的所有 SQL 要么全部执行成功,要么全部不执行。数据库事务具有 ACID 特性:

  • Atomicity:原子性
  • Consistency:一致性
  • Isolation:隔离性
  • Durability:持久性 ‍ 数据库事务可以并发执行,而数据库系统从效率考虑,对事务定义了不同的隔离级别。SQL 标准定义了 4 种隔离级别,分别对应可能出现的数据不一致的情况:
Isolation Level 脏读(Dirty Read) 不可重复读(Non Repeatable Read) 幻读(Phantom Read)
Read Uncommitted Yes Yes Yes
Read Committed - Yes Yes
Repeatable Read - - Yes
Serializable - - -

‍ 对应用程序来说,数据库事务非常重要,很多运行着关键任务的应用程序,都必须依赖数据库事务保证程序的结果正常。

举个例子:假设小明准备给小红支付 100,两人在数据库中的记录主键分别是 123 和 456,那么用两条 SQL 语句操作如下:

UPDATE accounts SET balance = balance - 100 WHERE id = 123 AND balance >= 100;
UPDATE accounts SET balance = balance + 100 WHERE id = 456;
1
2

这两条语句必须以事务方式执行才能保证业务的正确性,因为一旦第一条 SQL 执行成功而第二条 SQL 失败的话,系统的钱就会凭空减少 100,而有了事务,要么这笔转账成功,要么转账失败,双方账户的钱都不变。

这里我们不讨论详细的 SQL 事务,那是数据库教程里的内容,本文是讲 JDBC 的。 ‍

# JDBC 中的事务

要在 JDBC 中执行事务,本质上就是如何把多条 SQL 包裹在一个数据库事务中执行。我们来看 JDBC 的事务代码:

Connection conn = openConnection();
try {
    // 关闭自动提交:
    conn.setAutoCommit(false);
    // 执行多条 SQL 语句:
    insert(); 
    update(); 
    delete();
    // 提交事务:
    conn.commit();
} catch (SQLException e) {
    // 回滚事务:
    conn.rollback();
} finally {
    conn.setAutoCommit(true);
    conn.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

‍ 代码说明:

  1. 开启事务的关键代码是 conn.setAutoCommit(false),表示关闭自动提交。
  2. 提交事务的代码在执行完指定的若干条 SQL 语句后,调用 conn.commit()。
  3. 要注意事务不是总能成功,如果事务提交失败,会抛出 SQL 异常(也可能在执行 SQL 语句的时候就抛出了),此时我们必须捕获并调用 conn.rollback() 回滚事务。
  4. 最后,在 finally 中通过 conn.setAutoCommit(true) 把 Connection 对象的状态恢复到初始值。 ‍ 实际上,默认情况下,我们获取到 Connection 连接后,总是处于“自动提交”模式,也就是每执行一条 SQL 都是作为事务自动执行的,这也是为什么前面几节我们的更新操作总能成功的原因:因为默认有这种“隐式事务”。

只要关闭了 Connection 的 autoCommit,那么就可以在一个事务中执行多条语句,事务以 commit() 方法结束。 ‍

# 设定事务的隔离级别

如果要设定事务的隔离级别,可以使用如下代码:

// 设定隔离级别为 READ COMMITTED:
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
1
2

如果没有调用上述方法,那么会使用数据库的默认隔离级别。MySQL 默认隔离级别是 REPEATABLE_READ。

# JDBC Batch

使用 JDBC 操作数据库的时候,经常会执行一些批量操作:

例如,一次性给会员增加可用优惠券若干,我们可以执行以下 SQL 代码:

INSERT INTO coupons (user_id, type, expires) VALUES (123, 'DISCOUNT', '2030-12-31');
INSERT INTO coupons (user_id, type, expires) VALUES (234, 'DISCOUNT', '2030-12-31');
INSERT INTO coupons (user_id, type, expires) VALUES (345, 'DISCOUNT', '2030-12-31');
INSERT INTO coupons (user_id, type, expires) VALUES (456, 'DISCOUNT', '2030-12-31');
...
1
2
3
4
5

‍ 实际上执行 JDBC 时,因为只有占位符参数不同,所以 SQL 实际上是一样的:

for (var params : paramsList) {
    PreparedStatement ps = conn.preparedStatement("INSERT INTO coupons (user_id, type, expires) VALUES (?,?,?)");
    ps.setLong(params.get(0));
    ps.setString(params.get(1));
    ps.setString(params.get(2));
    ps.executeUpdate();
}
1
2
3
4
5
6
7

通过一个循环来执行每个 PreparedStatement 虽然可行,但是性能很低。SQL 数据库对 SQL 语句相同,但只有参数不同的若干语句可以作为 batch 执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个 SQL。

在 JDBC 代码中,我们可以利用 SQL 数据库的这一特性,把同一个 SQL 但参数不同的若干次操作合并为一个 batch 执行。我们以批量插入为例,示例代码如下:

try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
    // 对同一个 PreparedStatement 反复设置参数并调用 addBatch():
    for (Student s : students) {
        ps.setString(1, s.name);
        ps.setBoolean(2, s.gender);
        ps.setInt(3, s.grade);
        ps.setInt(4, s.score);
        ps.addBatch(); // 添加到 batch
    }
    // 执行 batch:
    int[] ns = ps.executeBatch();
    for (int n : ns) {
        System.out.println(n + " inserted."); // batch 中每个 SQL 执行的结果数量
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

执行 batch 和执行一个 SQL 不同点在于:

  1. 需要对同一个 PreparedStatement 反复设置参数并调用 addBatch(),这样就相当于给一个 SQL 加上了多组参数,相当于变成了“多行”SQL。
  2. 第二个不同点是调用的不是 executeUpdate(),而是 executeBatch(),因为我们设置了多组参数,相应地,返回结果也是多个 int 值,因此返回类型是 int[],循环 int[] 数组即可获取每组参数执行后影响的结果数量。

# 总结

JDBC 提供了事务的支持,使用 Connection 可以开启、提交或回滚事务。

使用 JDBC 的 batch 操作会大大提高执行效率,对内容相同,参数不同的 SQL,要优先考虑 batch 操作。

上次更新: 2025/6/3 09:31:54
JDBC 更新
JDBC 连接池

← JDBC 更新 JDBC 连接池→

最近更新
01
学点统计学:轻松识破一本正经的胡说八道
06-05
02
2025 年 5 月记
05-31
03
《贫穷的本质》很棒,但可能不适合你
05-27
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式