1. 概述

在使用Java和JDBC操作数据库时,我们经常需要将多个SQL语句作为单个操作执行。这能帮助我们提升应用性能、保证操作的原子性,或更有效地管理复杂工作流。

本教程将探讨在JDBC中执行多条SQL语句的多种方式。我们将通过使用Statement对象、批处理和存储过程等示例,展示如何高效执行多个SQL查询。本教程使用MySQL作为数据库。

2. JDBC与数据库环境搭建

在深入代码前,我们先确保项目配置正确且数据库已就绪。

2.1. Maven依赖

首先在pom.xml中添加MySQL JDBC驱动依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

2.2. 数据库配置

创建名为user_db的MySQL数据库和users表,后续将演示对表执行多条插入语句:

(此处应有数据库表结构示意图,展示users表的id、name、email字段)

完成数据库配置后,即可使用JDBC执行多条SQL语句。

3. 在Java中批量执行SQL语句

执行多语句前,必须确保数据库连接配置允许批量操作。对于MySQL,连接URL需添加allowMultiQueries=true属性。首先设置连接:

public Connection getConnection() throws SQLException {
    String url = "jdbc:mysql://localhost:3306/user_db?allowMultiQueries=true";
    String username = "admin";
    String password = "securepass";

    return DriverManager.getConnection(url, username, password);
}

此配置确保MySQL接受用分号分隔的多条SQL语句。默认情况下,MySQL不允许多语句执行,除非连接字符串包含allowMultiQueries=true

在JDBC中执行多语句主要有三种方法:

3.1. 使用Statement对象

Statement对象允许我们将多条SQL查询合并为用分号分隔的单个字符串执行。以下是向users表插入多条记录的示例:

public boolean executeMultipleStatements() throws SQLException {
    String sql = "INSERT INTO users (name, email) VALUES ('Alice', '[email protected]');" +
      "INSERT INTO users (name, email) VALUES ('Bob', '[email protected]');";

    try (Statement statement = connection.createStatement()) {
        statement.execute(sql);
        return true;
    }
}

验证功能的测试用例:

@Test
public void givenMultipleStatements_whenExecuting_thenRecordsAreInserted() throws SQLException {
    boolean result = executeMultipleStatements(connection);
    assertTrue(result, "语句应成功执行");

    try (Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(
          "SELECT COUNT(*) AS count FROM users WHERE name IN ('Alice', 'Bob')");) {
        resultSet.next();
        int count = resultSet.getInt("count");
        assertEquals(2, count, "应插入两条记录");
    }
}

⚠️ 注意:在单次execute()调用中执行多SQL语句的行为因数据库而异:

  • ✅ MySQL:需添加allowMultiQueries=true
  • ✅ PostgreSQL/SQL Server:默认支持
  • ❌ Oracle/H2:不支持,需逐条执行或使用批处理

3.2. 使用批处理

当多条语句不需要作为原子单元执行时,批处理是更高效的方案:

public int[] executeBatchProcessing() throws SQLException {
    try (Statement statement = connection.createStatement()) {
        connection.setAutoCommit(false);

        statement.addBatch("INSERT INTO users (name, email) VALUES ('Charlie', '[email protected]')");
        statement.addBatch("INSERT INTO users (name, email) VALUES ('Diana', '[email protected]')");

        int[] updateCounts = statement.executeBatch();
        connection.commit();

        return updateCounts;
    }
}

验证功能的测试用例:

@Test
public void givenBatchProcessing_whenExecuting_thenRecordsAreInserted() throws SQLException {
    int[] updateCounts = executeBatchProcessing(connection);
    assertEquals(2, updateCounts.length, "批处理应执行两条语句");

    try (Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(
          "SELECT COUNT(*) AS count FROM users WHERE name IN ('Charlie', 'Diana')");) {
        resultSet.next();
        int count = resultSet.getInt("count");
        assertEquals(2, count, "应通过批处理插入两条记录");
    }
}

3.3. 处理存储过程

存储过程预编译SQL代码并存储在数据库中,允许单次调用执行多条语句。首先创建存储过程:

DELIMITER //

CREATE PROCEDURE InsertMultipleUsers()
BEGIN
    INSERT INTO users (name, email) VALUES ('Eve', '[email protected]');
    INSERT INTO users (name, email) VALUES ('Frank', '[email protected]');
END //

DELIMITER ;

调用存储过程的Java代码:

public boolean callStoredProcedure() throws SQLException {
    try (CallableStatement callableStatement = connection.prepareCall("{CALL InsertMultipleUsers()}")) {
        callableStatement.execute();
        return true;
    }
}

验证功能的测试用例:

@Test
public void givenStoredProcedure_whenCalling_thenRecordsAreInserted() throws SQLException {
    boolean result = callStoredProcedure(connection);
    assertTrue(result, "存储过程应成功执行");

    try (Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(
          "SELECT COUNT(*) AS count FROM users WHERE name IN ('Eve', 'Frank')");) {
        resultSet.next();
        int count = resultSet.getInt("count");
        assertEquals(2, count, "存储过程应插入两条记录");
    }
}

3.4. 执行多条SELECT语句

前面展示了多条INSERT语句,现在演示如何执行多条SELECT语句。使用statement.getMoreResults()方法遍历多个结果集:

public List<User> executeMultipleSelectStatements() throws SQLException {
    String sql = "SELECT * FROM users WHERE email = '[email protected]';" +
      "SELECT * FROM users WHERE email = '[email protected]';";

    List<User> users = new ArrayList<>();

    try (Statement statement = connection.createStatement()) {
        statement.execute(sql); // 执行多条查询

        do {
            try (ResultSet resultSet = statement.getResultSet()) {
                while (resultSet != null && resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    String email = resultSet.getString("email");
                    users.add(new User(id, name, email));
                }
            }
        } while (statement.getMoreResults());
    }
    return users;
}

statement.getMoreResults()方法将游标移动到下一个结果,返回true表示下一个结果是ResultSetfalse表示更新计数或无更多结果。

验证功能的测试用例:

@Test
public void givenMultipleSelectStatements_whenExecuting_thenCorrectUsersAreFetched() 
  throws SQLException {
    MultipleSQLExecution execution = new MultipleSQLExecution(connection);
    execution.executeMultipleStatements();

    List<User> users = execution.executeMultipleSelectStatements();

    // 验证获取了两个用户且名称匹配
    assertThat(users)
      .hasSize(2)
      .extracting(User::getName)
      .containsExactlyInAnyOrder("Alice", "Bob");
}

4. 总结

在单次JDBC调用中执行多条SQL语句能提升性能和代码可读性。我们探讨了三种实现方式:使用Statement对象、批处理和存储过程。每种方法适用场景不同:

  • Statement:简单粗暴的多语句执行
  • 批处理:高性能批量操作
  • 存储过程:复杂业务逻辑封装

理解这些技术后,我们可以根据性能或可维护性需求,更合理地设计数据库操作结构,优化应用效率。