从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

  • JavaSenior

  • JavaEE

    • JavaEE概念

    • Ant

    • Maven

    • 日志

    • Junit

    • JDBC

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

    • Java
  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • 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操作。

‍

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/3/30 09:46:17
JDBC更新
JDBC连接池

← JDBC更新 JDBC连接池→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式