JDBC 事务和批处理 详解(通俗易懂)

news2025/2/1 2:00:40

目录

一、前言

二、事务

        1.事务介绍 : 

        2.事务处理 : 

                Δ准备工作

                Δ不使用事务的情况            

                Δ使用事务的情况

三、批处理

        1.介绍 :         

        2.常用方法 : 

        3.应用 : 

        4.源码分析(JDK17.0版本) : 

四、总结


一、前言

  • 第四节内容,up主要和大家分享一下JDBC——事务,批处理方面的内容。PS : 若大家对事务没有任何认识,先去阅读up之前的MySQL——事务的博文。
  • 注意事项——代码中的注释也很重要;不要眼高手低;点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
  • 良工不示人以朴,所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。 感谢阅读!

二、事务

        1.事务介绍 : 

        JDBC程序中,当一个Connection(实现类)对象被创建后,默认情况下是自动提交事务即每次成功执行一个SQL,都会向对应的数据库自动提交,而不能回滚。为了让多个SQL在JDBC中作为一个整体来执行,我们就需要用到“事务”,以保证数据的一致性

        可以使用Connection接口中的setAutoCommit(false)方法动态绑定后,实际为实现类的方法),传入一个false,可以取消自动提交事务;当所有SQL都成功执行后,可以调用Connection接口的commit方法提交事务;若中途出现失败或异常,则可以调用Connection接口的rollback方法回滚事务

        2.事务处理 : 

                Δ准备工作

                以“转账”操作为栗。
                我们先来建一张账户表accounts,代码如下 : 

CREATE TABLE IF NOT EXISTS `accounts`(
		`id` INT PRIMARY KEY AUTO_INCREMENT,
		`name` VARCHAR(64) NOT NULL DEFAULT '',
		`balance` DECIMAL(16,2) NOT NULL DEFAULT 0.0
) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;

INSERT INTO accounts(`name`,balance)
		VALUES
		('Cyan', 1000),
		('Five', 1900),
		('Alice', 2100);
		
SELECT * FROM accounts;

                账户表效果如下  :

                Δ不使用事务的情况            

                先在不使用事务的情况下进行转账操作,看看会出现什么问题:
                令Cyan转给Five 200块,最终Cyan应该是800,Five是2100;但是,假设在Cyan发出转账后,Five收取转账失败了。那会发生什么结果?
                up以NoTransaction类为演示类,代码如下 : 

package transaction;

import utils.JDBCUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class NoTransaction {
    public static void main(String[] args) throws ClassNotFoundException {
    //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

    //2.获取连接
        Connection connection = null;

    //3.执行SQL
        PreparedStatement preparedStatement = null;
        String sql1 = "UPDATE accounts " +
                            "SET balance = balance - 200 " +
                            "WHERE `name` = ?;";
        String sql2 = "UPDATE accounts " +
                            "SET balance = balance + 200 " +
                            "WHERE `name` = ?;";

        try {
            connection = JDBCUtils.getConnection();

            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.setString(1,"Cyan");
            /** 若在初始化preparedStatement对象时,已经与要执行的SQL绑定,则执行时不能再传入SQL参数! */
            preparedStatement.executeUpdate();

            //抛出一个算术异常(ArithmeticException),阻止第二条SQL语句的执行
            int i = 1 / 0;
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.setString(1,"Five");
            /** 若在初始化preparedStatement对象时,已经与要执行的SQL绑定,则执行时不能再传入SQL参数! */
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
    //4.释放资源
            JDBCUtils.close(null,preparedStatement,connection);
        }
    }
}

                程序如我们所料,在int i = 1/0; 处出现算术异常,如下图所示 : 

                此时如果我们查询accounts表,会发现出大问题了!如下图所示 : 

                Cyan凭空消失了200块钱😱。原因也很简单:发出转账操作的DML顺利执行,而接收转账操作的DML——由于异常的抛出——没有顺利执行;但是由于JDBC默认是自动提交事务的,因此发出转账的操作对数据库产生了永久的影响(事务的持久性)。

                Δ使用事务的情况

                使用事务后,我们可以轻松解决以上问题。
                up以WithTransaction类为演示类,代码如下 : 

package transaction;

import utils.JDBCUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class WithTransaction {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2.获取连接
        Connection connection = null;

        //3.执行SQL
        PreparedStatement preparedStatement = null;
        String sql1 = "UPDATE accounts " +
                "SET balance = balance - 200 " +
                "WHERE `name` = ?;";
        String sql2 = "UPDATE accounts " +
                "SET balance = balance + 200 " +
                "WHERE `name` = ?;";

        try {
            connection = JDBCUtils.getConnection();
            /** 开启一个事务 */
            connection.setAutoCommit(false);

            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.setString(1,"Cyan");
            preparedStatement.executeUpdate();

            //抛出一个算术异常(ArithmeticException),试图阻止第二条SQL语句的执行
            int i = 1 / 0;
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.setString(1,"Five");
            preparedStatement.executeUpdate();

            /** 若没有出现异常,则在SQL执行完毕后,提交事务。*/
            connection.commit();
        } catch (SQLException e) {
            /**  若事务执行过程中发生异常,捕获异常后进行回滚,
                默认回滚到事务开启时的状态。 */
            try {
                connection.rollback();
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
            throw new RuntimeException(e);
        } finally {
            //4.释放资源
            JDBCUtils.close(null,preparedStatement,connection);
        }
    }
}

                仍然是抛出一个算术异常,如下图所示 : 

                这时候如果我们查询accounts表,会发生数据没有发生变化,如下图所示 : 

                显然,由于事务机制的加入,我们可以轻松避免错误情况的发生。那么,正确的情况又如何呢?接下来,我们将制造异常的int i = 1/0; 语句给注释掉,如下图所示 : 

                再次尝试执行代码 : 

                可以看到,无异常抛出。再次查询accounts表,如下 : 

                🐂🍺,这下Cyan的💴好歹是“死得其所”了(bushi)。 


三、批处理

        1.介绍 :         

        当需要成批地执行DML时,可以采用Java提供的批量更新机制。这一机制允许多条SQL一次性地提交给数据库进行批量处理,通常比单独提交SQL效率更高。

        Java批处理机制往往与PreparedStatement一起搭配使用,既可以减少编译次数(预处理),又可以减少运行次数,极大地提高了SQL执行效率。

        2.常用方法 : 

        addBatch() : 向集合中添加需要批量处理的SQL或者参数;

       executeBatch() : 执行批量处理语句;

        clearBatch() : 清空当前批处理包内的语句;

        PS : 

        ΔJDBC连接MySQL时,如果要使用批处理功能,需要在url 中加入参数?rewriteBatchedStatements=true

        3.应用 : 

                为了明确的看到批处理机制对执行效率的提升,up打算统计大量DML语句的执行时间对比在未使用批处理机制和使用批处理机制后,DML语句执行时间的差异。PS : 可以使用System类的静态方法currentTimeMillis()。
                还是先来建一张表,up以狗表(dogs)为例,代码如下 : 

CREATE TABLE IF NOT EXISTS `dogs`(
		`id` MEDIUMINT PRIMARY KEY AUTO_INCREMENT,
		`name` VARCHAR(64) NOT NULL DEFAULT ''
) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;

SELECT * FROM dogs;

                狗表效果如下 : 

                现要求向狗表中加入10000只🐕,看看需要多长时间执行完;
                up以NoBatch类为演示类,代码如下 : 

package batch;

import utils.JDBCUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class NoBatch {
    public static void main(String[] args) throws ClassNotFoundException {
//JDBC核心四部曲
    //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

    //2.获取连接
        Connection connection = null;

    //3.执行SQL
        PreparedStatement preparedStatement = null;
        String sql = "INSERT INTO dogs " +
                        "VALUES " +
                        "(null,?);";

        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);

            preparedStatement.setString(1, "狗");

            System.out.println("开始执行万🐕SQL!");
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                preparedStatement.executeUpdate();
            }
            long end = System.currentTimeMillis();
            System.out.println("万🐕SQL执行完毕,一共用时:" + (end - start) + "ms");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
    //4.释放资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
}

                运行效果 : 

                可以看到,用了足足2s多。 

                我们再来看看用了批处理机制后性能会有多大提升:记住,别忘了在url中加入?rewriteBatchedStatements=true参数,如下图所示 : 

                然后,我们清空dogs表中的全部数据 : 

TRUNCATE TABLE dogs;

                接下来测试使用批处理机制后,万🐕SQL的执行时间。
                up以WithBatch类为演示类,代码如下 : 

package batch;

import utils.JDBCUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class WithBatch {
    public static void main(String[] args) throws ClassNotFoundException {
//JDBC核心四部曲
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2.获取连接
        Connection connection = null;

        //3.执行SQL
        PreparedStatement preparedStatement = null;
        String sql = "INSERT INTO dogs " +
                "VALUES " +
                "(null,?);";

        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);

            preparedStatement.setString(1, "狗");

            System.out.println("开始执行万🐕SQL!");
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                preparedStatement.addBatch();
                //批处理包中每满1000条SQL,就一次性发送给MySQL进行处理。
                if ((i + 1) % 1000 == 0) {
                    preparedStatement.executeBatch();
                    preparedStatement.clearBatch(); //执行后及时清理当前集合内的SQL
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("万🐕SQL执行完毕,一共用时:" + (end - start) + "ms");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //4.释放资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
}

                运行结果 : 

                可以看到,从2204ms到113ms,性能的提升是肉眼可见的。 

        4.源码分析(JDK17.0版本) : 

                如下图所示,在addBatch()方法处下断点 : 

                接着,跳入addBatch方法,如下图所示 : 

                注意,checkAllParametersSet()方法就是对你通过setXxx传入的参数进行校验(对?的赋值),同时也进行一些预编译的工作。
                其实,我们跳入的addBatch方法是位于ClientPreparedStatement类中的,那么,ClientPreparedStatement类又是何方神圣呢?
                我们来看一下它的类图就明白了,如下所示 : 

                挑重点说,ClientPreparedStatement类直接继承自StatementImpl类,并且间接实现了PreparedStatement接口。 
                继续,我们可以发现这个addBatch方法并没有涉及到什么集合,但是它最后又调用了一个带参的addBatch方法,如下 : 

                我们接着追进去看看,如下 : 

                追进去后的addBatch方法位于AbstractQuery类中(该类实现了Query接口),我们还是把重点放在代码本身上,可以看到,这个addBatch方法底层,其实是new了一个ArrayList类的对象,并且将当前的SQL语句加入到了ArrayList集合中
                不难猜出,batchedArgs是一个List类型或者ArrayList类型,看下它的源码 : 

                果然如此。
                再往下调用的就是ArrayList类的add方法了,这里不再赘述。up之前已经出过一篇专门分析ArrayList源码的文章,非常详细,有兴趣的小伙伴儿可以去看看。 

                继续,当我们将第一句SQL添加到ArrayList集合中后,便可以发现elementData数组中已经有了一个元素,如下图所示 : 

                当然,关于elementData数组的本质及它的扩容问题,还是建议大家去看up的ArrayList类源码分析。 这里我们只需要明白——批处理机制的本质,就是将多条SQL打包到了ArrayList集合中,然后统一发送给MySQL,因此减少了执行次数。比如我们这里有10000条SQL,每批执行1000条,只需要执行10次即可;若没有批处理机制,那就是执行10000条,挨个执行,自然网络开销就上来了。


四、总结

  • 🆗,以上就是JDBC系列博文第四节的全部内容了。
  • 总结一下,为了保证进行DML操作时数据的一致性,Java提供了事务机制,只有一组SQL都成功执行才提交事务,否则回滚。而为了提高多条DML的执行效率,Java又提供了批处理机制,通过Debug我们也发现,批处理机制的本质就是将多条SQL打包到集合中,成批成批地发送给MySQL,减少了网络传输的次数,批处理机制与PreparedStatement配合使用,可以极大地提高SQL执行效率。
  • 下一节内容——JDBC 连接池,我们不见不散。感谢阅读!

        System.out.println("END-----------------------------------------------------------------------------"); 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/631498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里背调,征信不好也会被pass

大厂背调&#xff0c;我一直认为是唬人的&#xff0c;走下流程而已&#xff0c;没想到这么严格。这次提供的背调信息&#xff0c;我填写了上家公司三个联系人&#xff0c;HR、领导、同事&#xff1b;上上家公司三个联系人&#xff0c;HR、领导、同事。根据朋友的反馈来看&#…

python 第五章 列表list [ ]

系列文章目录 第一章 初识python 第二章 变量 第三章 基础语句 第四章 字符串str 文章目录 5.1列表的应用场景5.2列表的格式5.3列表的常用操作查找下标函数查找函数index()count()len() 判断是否存在innot in 增加append()extend()insert() 删除delpop()remove()clear() 清空列…

Java并发回顾

树叶柔和爽朗的呼吸 诗人一路吹着口哨回家 一路踢着石子妙想连篇 感到夕阳和晚风自古多情 自己现在和将来 都是个幸福的人 系列文章目录 Java常见知识点汇总Java集合回顾Java并发回顾… 文章目录 系列文章目录什么是线程和进程?线程与进程的关系,区别及优缺点&#xff1f;图解…

Tcp的三次握手及netty和实际开发如何设置全连接队列参数

上图 第一次握手&#xff0c;client 发送 SYN 到 server&#xff0c;状态修改为 SYN_SEND&#xff0c;server 收到&#xff0c;状态改变为 SYN_REVD&#xff0c;并将该请求放入 sync queue 队列 第二次握手&#xff0c;server 回复 SYN ACK 给 client&#xff0c;client 收到…

【Prometheus】mysqld_exporter采集+Grafana出图+AlertManager预警

前提环境&#xff1a;已经安装和配置好prometheus server 所有组件对应的版本&#xff1a; prometheus-2.44.0 mysqld_exporter-0.14.0 grafana-enterprise-9.1.2-1.x86_64.rpm alertmanager-0.25.0 prometheus-webhook-dingtalk-2.1.0 简介 mysql_exporter是用来收集MysQL或…

spring 事务超时

Transactional(timeout 10) 表示设置事务的超时时间为10秒 表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话&#xff0c;最终结果会选择回滚 默认值-1&#xff0c;表示没有时间限制。 如果最后一条DML语句后面还有很多业务逻辑&#xff0c;这些业务代码执行的时间不…

setState详解

this. setState( [partialState], [callback]) 1.[partialState] :支持部分状态更改 this, setState({ x:100 //不论总共有多少状态&#xff0c;我们只修改了x&#xff0c;其余的状态不动 });callback :在状态更改/视图更新完毕后触发执行&#xff0c;也可以说只要执行了setS…

lightGBM的介绍

一、lightGBM的介绍 1.lightGBM的演进过程 2.AdaBoost算法 AdaBoost&#xff08;Adaptive Boosting&#xff09;是一种集成学习算法&#xff0c;通过组合多个弱分类器来构建一个强分类器。它是由Freund和Schapire在1996年提出的&#xff0c;是集成学习中最早被广泛应用的算法…

JDK8-1-Lambda表达式(5)-复合 Lambda 表达式

JDK8-1-Lambda表达式&#xff08;5&#xff09;-复合 Lambda 表达式 JDK8 在 java.util.function 包下定义了一些默认的 函数式接口 &#xff0c;如 Predicate、Consumer、Function、 Comparator &#xff08;在 java.util.包下&#xff09; &#xff0c;这些接口提供了一些复…

运营-21.常见的内容生产方式

常见的 内容生产方式 PGC&#xff08;Professionally-generated Content&#xff09;专业生产内容 传统的门户网站内容生产方式&#xff0c;内容多由官方工作人员身或者专业的内容创造者&#xff08;比如新闻记者等&#xff09;创造。 UGC&#xff08;User Generated Content&a…

【好书精读】网络是怎样连接的 浏览器生成消息

如果只是讲解 TCP/IP 、 以太网这些单独的技 术 &#xff0c; 读者就无法理解网络这个系统的全貌 &#xff1b; 如果无法理解网络的全貌 &#xff0c; 也 就无法理解每一种网络技术背后的本质意义 &#xff1b; 而如果无法理解其本质意义 &#xff0c; 就只能停留在死记硬背的…

MMDetection代码实战

title: mmdet代码实战 date: 2023-06-10 17:01:45 tags: [detection,pytorch] MMDetection代码实战 这一届主要讲解了&#xff0c;如何构建cfg&#xff0c;如何在目标检测上设置自己的配置文件。主要的思维导图如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下…

第3章“程序的机器级表示”:对齐(alignment)

许多计算机系统对基本数据类型的可允许地址做出了一些限制&#xff0c;要求某种类型的对象的地址必须是某个值 k k k &#xff08;通常是2、4 或 8&#xff09;的倍数。这种 对齐限制 简化了处理器和存储器系统之间接口的硬件设计。例如&#xff0c;假设一个处理器总是从存储器…

pikachu靶场-File Inclusion

文件包含漏洞概述 在web后台开发中&#xff0c;程序员往往为了提高效率以及让代码看起来更加简介&#xff0c;会使用”包含“函数功能。比如把一系列功能函数都写进function.php中&#xff0c;之后当某个文件需要调用的时候就直接在文件头上写上一句<?php include functio…

多叉树的构建,条件查询,修改删除节点

多叉树的构建查询&#xff0c;新增&#xff0c;修改&#xff0c;删除 文章目录 多叉树的构建查询&#xff0c;新增&#xff0c;修改&#xff0c;删除一&#xff0c;数据库表设计二、新增相对应数据库表的实体类三、新建多叉树树工具类四、多叉树树的条件查询五、多叉树的节点新…

关于【Git】push失败与使用小乌龟(TortoiseGit)时的一些报错解决方案

1.报错:No supported authentication methods available (server sent: publickey) 原因.小乌龟没有设置git路径&#xff0c;解决如下 将红框标注的地址改为自己的git安装地址即可。 2.使用git推送到远程仓库的时候报错Failed to connect to 127.0.0.1 port 7890: 拒绝连接 …

C# 让程序代码在固定的线程里运行

一、概述 在平时我们的开发中&#xff0c;多线程也是经常用到的&#xff0c;尤其是我们做上位机行业的&#xff0c;平时更是必不可少&#xff0c;在以前我做 Unity3d 开发时&#xff0c;其实并不用关心线程的问题&#xff0c;在 Unity 的开发中&#xff0c;所有代码基本都是单…

点云综述(整理自网络资源)

目录 一、什么是点云 二、如何获取点云 1、三维激光扫描仪 2、双目相机 双目测距基本原理 视差图 双目测距的优点与难点 3、RGB-D相机 RGB-D什么意思 RGB-D相机的分类 RGBD相机的缺点&#xff1a; RGBD相机的优点 三、点云有哪些研究方向 1、基于点云的分类 2、基于…

华为OD机试真题 JavaScript 实现【IPv4地址转换成整数】【2023 B卷 100分】

一、题目描述 存在一种虚拟 IPv4 地址&#xff0c;由4小节组成&#xff0c;每节的范围为0~255&#xff0c;以#号间隔&#xff0c; 虚拟 IPv4 地址可以转换为一个32位的整数&#xff0c;例如&#xff1a; 128#0#255#255&#xff0c;转换为32位整数的结果为2147549183&#xff0…

【深入理解函数栈帧:探索函数调用的内部机制】

本章我们要介绍的不是数学中的函数&#xff0c;而是C语言中的函数哟&#xff01; 本章重点 了解汇编指令深刻理解函数调用过程 样例代码&#xff1a; #include <stdio.h> int MyAdd(int a, int b) {int c 0;c a b;return c; }int main() {int x 0xA;int y 0xB;int…