阅读本文需要的先修知识:

  • 最基本的SQL语句
  • 最基本的JDBC操作(如插入单条记录)

如急需使用请直接看最后一段代码。

在JDBC中,对记录进行修改操作最简单的方法是使用executeUpdate()方法,但该方法中的参数只能是单条SQL语句,其实对于需要一次执行多条语句的情况,JDBC也提供了批处理的机制。

1.事务

批处理基于事务处理,JDBC提供了两个方法void commit()void rollback(),这两个函数的用法正如大部分SQL数据库中提供的事务处理语句一样,commit()方法用来提交多条语句,rollback()方法用来回滚至执行本次事务之前的状态。

请看如下代码:

public static void main(String[] args) {
        Connection conn;
        Statement stmt;

        try {

            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            //DB_URL,USER,PASS均为事先定义好的字符串,分别代表数据库地址,登录用户名,密码
            stmt = conn.createStatement();

            conn.setAutoCommit(false);
            stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(1, 'Chandler', '1111111')");
            stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(2, 'Joey', '2222222')");
            stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(3, 'Rachel', '3333333')");
            conn.commit();

            stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(4, 'Monica', '4444444')");
            stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(5, 'Ross', '5555555')");
            stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(6, 'Phoebe', '666666')");

            stmt.close();
            conn.close();

        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在执行命令之前,我们首先调用了一个参数为falsesetAutoCommit()方法,这个方法的作用是使得在其之后执行的命令不会立即被提交给数据库,而是等到下一次调用commit()方法时,才一次性全部提交。
在上面的代码中,我们首先执行了3条插入语句,然后进行了一次提交,之后又执行了3条插入语句,不同的是这3条插入语句执行后没有进行提交。执行这个程序之后,结果是这样的。

mysql> select * from test;
+----+----------+---------+
| id | name     | tel     |
+----+----------+---------+
|  1 | Chandler | 1111111 |
|  2 | Joey     | 2222222 |
|  3 | Rachel   | 3333333 |
+----+----------+---------+
3 rows in set (0.00 sec)

可见后面3条插入命令因为还没有commit,所以是没有生效的。就这个结果来看,利用这样的方法,我们就可以先执行多条插入语句,再进行一次commit,达到一次插入多条记录的效果。但事实上,这样和不使用事务没有太大的区别,性能也没有什么提高,真正要实现批量插入,我们还需要借助JDBC的批处理机制。

2.批处理

在这里我们主要需要使用两个方法,分别是void addBatch(String command)int[] executeBatch()。我们通过对上面的代码做一些改动来探究这两个方法的用法。

public static void main(String[] args) {
        Connection conn;
        Statement stmt;

        try {

            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            //DB_URL,USER,PASS均为事先定义好的字符串,分别代表数据库地址,登录用户名,密码
            stmt = conn.createStatement();

            conn.setAutoCommit(false);
            stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(1, 'Chandler', '1111111')");
            stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(2, 'Joey', '2222222')");
            stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(3, 'Rachel', '3333333')");
            stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(4, 'Monica', '4444444')");
            stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(5, 'Ross', '5555555')");
            stmt.addBatch("INSERT INTO test(id, name, tel) VALUES(6, 'Phoebe', '666666')");

            int[] counts = stmt.executeBatch(); //执行Batch中的全部语句
            conn.commit();                      //提交到数据库
            for (int i : counts) {
                if (i == 0) {
                    conn.rollback();
                }
            }

            conn.setAutoCommit(true);   //在完成批量操作后恢复默认的自动提交方式,提高程序的可扩展性

            stmt.close();
            conn.close();

        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

addBatch()方法每调用一次,就相当于往一个假想的“批处理”中添加了一条语句,这些语句在下一次调用 executeBatch()方法时一次性全部执行,在此之后,我们再次调用一个commit()将所作的更改提交到数据库。
executeBatch()方法的返回值是一个int数组,里面保存了本次执行的每条语句的返回值,即受到影响的记录的行数,在本例中,数组中的所有值均应为1,如果为0则说明插入失败,我们可以选择进行回滚或者报错。

3.预备语句

每次调用addBatch()方法时都需要输入一长串SQL语句显得十分繁琐,在操作列数比较多的表时就更是如此,为了避免这样的情况,我们可以使用预备语句。
预备语句的用法有点类似于printf()的用法,当我们使用printf进行输出时,往往会在字符串中插入几个像%d%c这样的占位符,至于这些位置具体的值,我们则在字符串后面再专门指定。
预备语句的占位符没有按类型进行区分,只有一种——?,请看如下代码:

PreparedStatement pstm = conn.prepareStatement("INSERT INTO test(id, name, tel) VALUES(?, ?, ?")
pstm.setInt(1, 1);
pstm.setString(2, 'Chandler');
pstm.setString(3, '1111111');
pstm.executeUpdate();

首先我们使用带占位符?的SQL语句初始化一个PreparedStatement对象,然后分别使用setInt()方法和setString()方法给对应的位置填值,除了这两种方法还有很多其他类型的赋值方法,具体可以查阅官方文档或者利用IDE的自动补全功能进行查看,这一类方法的参数都是类似的,第一个参数指明要给第几个?进行赋值,第二个参数要赋的;在给所有的位置赋值之后,我们调用executeUpdate()方法执行这条语句。
上面代码的功能和下面的等同:

Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO test(id, name, tel) VALUES(1, 'Chandler', '1111111')");

表面看来下面使用普通语句的方法更简洁,但当我们要操作的记录数变多,乃至成千上万条时,预备语句的优势就会体现出来。最后,我们将一开始的程序使用预备语句+批量更改+事务重写一遍:

public static void main(String[] args) {
        Connection conn;
        PreparedStatement pstm;

        try {

            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            //DB_URL,USER,PASS均为事先定义好的字符串,分别代表数据库地址,登录用户名,密码

            conn.setAutoCommit(false);
            pstm = conn.prepareStatement("INSERT INTO test(id, name, tel) VALUES(?, ?, ?)");

            pstm.setInt(1, 1);
            pstm.setString(2, "Chandler");
            pstm.setString(3, "1111111");
            pstm.addBatch();

            pstm.setInt(1, 2);
            pstm.setString(2, "Joey");
            pstm.setString(3, "2222222");
            pstm.addBatch();

            pstm.setInt(1, 3);
            pstm.setString(2, "Rachel");
            pstm.setString(3, "3333333");
            pstm.addBatch();

            pstm.setInt(1, 4);
            pstm.setString(2, "Monica");
            pstm.setString(3, "4444444");
            pstm.addBatch();

            pstm.setInt(1, 5);
            pstm.setString(2, "Ross");
            pstm.setString(3, "5555555");
            pstm.addBatch();

            pstm.setInt(1, 6);
            pstm.setString(2, "Phoebe");
            pstm.setString(3, "666666");
            pstm.addBatch();

            int[] counts = pstm.executeBatch(); //执行Batch中的全部语句
            conn.commit();                      //提交到数据库
            for (int i : counts) {
                if (i == 0) {
                    conn.rollback();
                }
            }

            conn.setAutoCommit(true);   //在完成批量操作后恢复默认的自动提交方式,提高程序的可扩展性

            pstm.close();
            conn.close();

        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

4. 总结

虽然在本文中我们举的例子是一次性插入六条数据,但我们更应该利用JDBC的批处理机制去执行一些更复杂的操作,比如WHERE条件不同的批量UPDATE操作,或者需要和for循环配合使用动态修改SQL语句的情况,等等。

参考文献
  1. Java核心技术·卷2:高级特性(原书第9版)(截止我写这篇文章时,已经出到第10版)
  2. MySQL必知必会
实验所用环境
  1. Windows 10(1809)
  2. jdk 1.8.0_101
  3. MySQL Sserver 8.0.13 for Win64 on x86_64

欢迎提出建议或意见
原创文章,转载请注明出处


2018-12-14更新:
感谢@风中的雪糕 和@p712long 指出问题,对文章做了少量修改。

使用JDBC一次执行多条语句(以MySQL为例)的更多相关文章

  1. 使用jdbc对数据库增删改查(Mysql为例)

    一.statement对象介绍 Statement对象的executeUpdate方法,用于向数据库发送增.删.改的sql语句,executeUpdate执行完后,将会返回一个整数. Statemen ...

  2. shell脚本中执行sql脚本(mysql为例)

    1.sql脚本(t.sql) insert into test.t value ("LH",88); 2.shell脚本(a.sh     为方便说明,a.sh与t.sql在同一目 ...

  3. MyBatis中如何一次执行多条语句(使用mysql数据库)

    解决办法不外乎有三个:1.多条sql分批执行:2.存储过程或函数调用:3.sql批量执行. MyBatis中如何一次执行多条语句(使用mysql数据库): 1.修改数据库连接参数加上allowMult ...

  4. mybatis 一次执行多条语句

    现在的一些互联网应用 为了提高性能,现在一般比较少的使用外键.不是不用,只是在创建数据库不标明外键关系,用程序去维护. 为了维护数据一致性,我们需要手动完成相关数据的删除 比如用户和用户的关注 当用户 ...

  5. jmert jdbc request支持执行多条sql语句并设置jdbc字符集

    1.jdbc request支持执行多条sql语句 在JDBC Connection Configuration中的sql连接字串中添加如下内容 allowMultiQueries=true 如下图: ...

  6. 通过JDBC进行简单的增删改查(以MySQL为例) 目录

    通过JDBC进行简单的增删改查(以MySQL为例) 目录 前言:什么是JDBC 一.准备工作(一):MySQL安装配置和基础学习 二.准备工作(二):下载数据库对应的jar包并导入 三.JDBC基本操 ...

  7. Java通过JDBC进行简单的增删改查(以MySQL为例)

    Java通过JDBC进行简单的增删改查(以MySQL为例) 目录: 前言:什么是JDBC 一.准备工作(一):MySQL安装配置和基础学习 二.准备工作(二):下载数据库对应的jar包并导入 三.JD ...

  8. [bigdata] 启动CM出现 “JDBC Driver class not found: com.mysql.jdbc.Driver” 以及“Error creating bean with name 'serverLogFetcherImpl'”问题的解决方法

    问题:“JDBC Driver class not found: com.mysql.jdbc.Driver”  通过以下命令启动cm [root@hadoop1 ~]# /etc/init.d/cl ...

  9. Oracle 一次执行多条语句

    在.Net使用多次方法一次执行多条语句都不成功, 百度了许久才找到正确的解决方案. Oracle执行多条语句的时候 不能有物理换行 写法对比: 如下写法是不成功. begin into t_test ...

随机推荐

  1. UWP 查找模板中的控件

    这个标题我也不知道咋起,意思说一下你就明白. 1. 对官方控件的模板进行定制修改,以满足多样化需求,还有漂亮的UI 比如ListView,GridView等. 2. 在设计的情况下并没有这个控件,而在 ...

  2. 友链&&日记

    上面友链,下面日记 友人链 最喜欢galgameの加藤聚聚 初三一本&&\(ACG\)姿势比我还丰厚的yx巨巨 更喜欢galgame的shadowice czx ZigZag胖胖 文文 ...

  3. [NOIP2018]保卫王国(树形dp+倍增)

    我的倍增解法吊打动态 \(dp\) 全局平衡二叉树没学过 先讲 \(NOIP\) 范围内的倍增解法. 我们先考虑只有一个点取/不取怎么做. \(f[x][0/1]\) 表示取/不取 \(x\) 后,\ ...

  4. JAVA多线程下载

    package com.jan.test; import java.io.File; import java.io.IOException; import java.io.RandomAccessFi ...

  5. centos7上mysql5.6版本主从复制

    做主从复制实验: 第一步:主服务器上操作 1.修改主服务器master: [root@localhost ~]# vim /etc/my.cnf server_id = 1  //[必须]服务器唯一I ...

  6. JavaScript 那些不经意间发生的数据类型自动转换

    JavaScript可以自由的进行数据类型转换,也提供了多种显式转换的方式.但是更多的情况下,是由JavaScript自动转换的,当然这些转换遵循着一定的规则,了解数据类型自由转换的规则是非常必要的. ...

  7. iOS开发之Todo List for Swift项目

    一直从事Windows Phone开发,但对iOS开发一直有所好奇,于是在MBP到手之际,顺手安装了Xcode.移动互联网开发的相似性,使得我能快速地了解和认识了iOS的开发框架体系,在看完了Appl ...

  8. 【sping揭秘】11、Java 平台上的AOP实现机制

    动态代理 Jdk1.3只有引入的动态代理机制,可以再运行期间,为相应的接口(必须得有接口)动态生成对应的代理对象 基于以上问题,我们可以将横切关注点逻辑封装到动态代理的invocationhandle ...

  9. How To Scan QRCode For UWP (1)

    本文将介绍实现一个类似于微信扫一扫功能的UI界面,后续会再实现具体的识别二维码的功能. 实例使用的Win10 SDK Version是Windows 10 Anniversary Edition(10 ...

  10. 从用户浏览器输入url到用户看到页面结果的过程,发生了什么事情?

    1.域名解析 域名解析的过程:  1).查询浏览器自身DNS缓存 2).若上面没有查找到,则搜索操作系统自身的dns缓存 3).若上面没有找到,则尝试读取hosts文件 4).若上面没有找到,向本地配 ...