Java之JDBC批量插入数据

本文详细介绍了使用批量操作和事务提交来显著提高MySQL数据库插入性能的方法。通过对比不同策略,展示了如何将10万条数据的插入时间从13秒减少到1.2秒,揭示了批量操作结合事务提交的高效性。

Github个人博客:https://joeyos.github.io

普通插入方式

10万条数据,耗时13秒。。。

private String url = "jdbc:mysql://localhost:3306/test01";
  private String user = "root";
  private String password = "123456";
  @Test
  public void Test(){
    Connection conn = null;
    PreparedStatement pstm =null;
    ResultSet rt = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection(url, user, password);   
      String sql = "INSERT INTO myTable values(?,?)";
      pstm = conn.prepareStatement(sql);
      Long startTime = System.currentTimeMillis();
      for (int i = 1; i <= 100000; i++) {
          pstm.setInt(1, i);
          pstm.setInt(2, i);
          pstm.executeUpdate();
      }
      Long endTime = System.currentTimeMillis();
      System.out.println("用时:" + (endTime - startTime));
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }finally{
      if(pstm!=null){
        try {
          pstm.close();
        } catch (SQLException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }
      if(conn!=null){
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }
    }
  }

事务提交

  • 设置conn.setAutoCommit(false);
  • 最后在所有命令执行完之后再提交事务conn.commit();
  • 10w条数据11秒。。。
private String url = "jdbc:mysql://localhost:3306/test01";
  private String user = "root";
  private String password = "123456";
  @Test
  public void Test(){
    Connection conn = null;
    PreparedStatement pstm =null;
    ResultSet rt = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection(url, user, password);   
      String sql = "INSERT INTO myTable values(?,?)";
      pstm = conn.prepareStatement(sql);
      Long startTime = System.currentTimeMillis();
      conn.setAutoCommit(false);
      for (int i = 1; i <= 100000; i++) {
          pstm.setInt(1, i);
          pstm.setInt(2, i);
          pstm.executeUpdate();
      }
      conn.commit();
      Long endTime = System.currentTimeMillis();
      System.out.println("用时:" + (endTime - startTime));
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }finally{
      if(pstm!=null){
        try {
          pstm.close();
        } catch (SQLException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }
      if(conn!=null){
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }
    }
  }

批量操作

  • 注意更改url = “jdbc:mysql://localhost:3306/test01?rewriteBatchedStatements=true”;
  • pstm.addBatch();代替pstm.executeUpdate();
  • 最后批量操作pstm.executeBatch();
  • 10w条数据1.3秒!!!

事务提交+批量操作

10w条数据大概1.2秒。

private String url = "jdbc:mysql://localhost:3306/test01?rewriteBatchedStatements=true";
  private String user = "root";
  private String password = "123456";
  @Test
  public void Test(){
    Connection conn = null;
    PreparedStatement pstm =null;
    ResultSet rt = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection(url, user, password);   
      String sql = "INSERT INTO myTable values(?,?)";
      pstm = conn.prepareStatement(sql);
      Long startTime = System.currentTimeMillis();
      conn.setAutoCommit(false);
      for (int i = 1; i <= 100000; i++) {
          pstm.setInt(1, i);
          pstm.setInt(2, i);
          pstm.addBatch();
      }
      pstm.executeBatch();
      conn.commit();
      Long endTime = System.currentTimeMillis();
      System.out.println("用时:" + (endTime - startTime));
    } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }finally{
      if(pstm!=null){
        try {
          pstm.close();
        } catch (SQLException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }
      if(conn!=null){
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }
    }
  }
<think>在Java中使用JDBC批量插入数据可以提高数据插入操作的效率,尤其是当需要插入大量数据时。批量插入可以减少与数据库的交互次数,从而显著减少网络传输开销和数据库处理时间。 ### 实现步骤: 1. 加载数据库驱动(对于JDBC 4.0及以上,通常可以自动加载)。 2. 建立数据库连接。 3. 创建PreparedStatement对象,使用参数化SQL语句。 4. 关闭自动提交(以便手动控制事务)。 5. 循环设置参数,并添加到批处理中。 6. 执行批处理。 7. 提交事务(如果之前关闭了自动提交)。 8. 处理异常,必要时回滚事务。 9. 关闭资源。 ### 示例代码: 以下是一个使用JDBC批量插入数据的示例代码: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BatchInsertExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/testdb"; String user = "username"; String password = "password"; Connection conn = null; PreparedStatement pstmt = null; try { // 1. 建立连接 conn = DriverManager.getConnection(url, user, password); // 2. 设置手动提交事务 conn.setAutoCommit(false); String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; pstmt = conn.prepareStatement(sql); // 3. 批量插入数据 for (int i = 1; i <= 1000; i++) { pstmt.setString(1, "User" + i); pstmt.setString(2, "user" + i + "@example.com"); pstmt.addBatch(); // 添加到批处理 // 每100条执行一次批处理,避免内存溢出 if (i % 100 == 0) { pstmt.executeBatch(); conn.commit(); // 提交事务 } } // 执行剩余的批处理 pstmt.executeBatch(); conn.commit(); // 提交事务 System.out.println("批量插入完成!"); } catch (SQLException e) { e.printStackTrace(); try { if (conn != null) { conn.rollback(); // 回滚事务 } } catch (SQLException ex) { ex.printStackTrace(); } } finally { // 4. 关闭资源 try { if (pstmt != null) pstmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } ``` ### 关键点解释: 1. **关闭自动提交**:通过`conn.setAutoCommit(false)`关闭自动提交,这样可以将多个插入操作作为一个事务处理,要么全部成功,要么全部失败(通过回滚)。 2. **批处理添加**:使用`addBatch()`方法将参数化的预编译语句添加到批处理中。 3. **分批执行**:在循环中,每100条记录执行一次批处理并提交,这样可以避免一次性添加太多数据导致内存溢出。 4. **事务提交**:在批处理执行后,需要手动提交事务(`conn.commit()`)。 5. **异常处理**:在catch块中打印异常,并回滚事务。在finally块中关闭资源,确保资源释放。 ### 注意事项: - **批处理大小**:根据实际情况调整批处理的大小(如每100条、500条等),以平衡内存使用和性能。 - **事务管理**:批量插入通常在一个事务中完成,如果插入数据量非常大,可能需要考虑分多个事务提交,以避免长事务和锁表问题。 - **数据库驱动**:不同的数据库(如MySQL、Oracle、PostgreSQL等)对批处理的支持可能有所不同,需要确保使用的驱动支持批处理操作。 - **内存管理**:如果一次性添加太多数据到批处理中,可能会导致内存溢出(OutOfMemoryError),因此建议分批次执行。 ### 相关问题: §§相关问题§§: 1. 在JDBC中,如何设置事务的隔离级别? 2. 使用PreparedStatement和Statement在批处理中有什么区别? 3. 如何处理JDBC批处理过程中的异常? 4. 在批量插入时,如何获取自动生成的主键?</think>### Java使用JDBC批量插入数据Java中,使用JDBC批量插入数据可以显著提高数据库操作效率,特别是当需要插入大量数据时。以下是完整的实现方法和代码示例: #### 方法实现 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BatchInsertExample { // 数据库连接信息 private static final String URL = "jdbc:mysql://localhost:3306/testdb"; private static final String USER = "username"; private static final String PASSWORD = "password"; public static void main(String[] args) { try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { // 1. 关闭自动提交(关键步骤!) conn.setAutoCommit(false); // 2. 准备SQL语句(使用占位符) String sql = "INSERT INTO employees (name, email, department) VALUES (?, ?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql)) { // 3. 批量设置参数 for (int i = 1; i <= 1000; i++) { pstmt.setString(1, "Employee_" + i); // name pstmt.setString(2, "emp" + i + "@company.com"); // email pstmt.setString(3, "Dept_" + (i % 5)); // department pstmt.addBatch(); // 添加到批处理 // 4. 分段执行(每100条执行一次) if (i % 100 == 0) { pstmt.executeBatch(); conn.commit(); // 提交事务 } } // 5. 执行剩余批处理 int[] updateCounts = pstmt.executeBatch(); conn.commit(); // 最终提交 System.out.println("批量插入完成,影响行数: " + updateCounts.length); } } catch (SQLException e) { e.printStackTrace(); // 实际应用中应添加事务回滚逻辑 } } } ``` #### 关键优化点 1. **事务管理**: - `conn.setAutoCommit(false)`:禁用自动提交(核心优化) - 手动分批提交事务(`conn.commit()`),避免单条提交的开销 2. **批处理机制**: - `addBatch()`:将参数化语句加入批处理队列 - `executeBatch()`:一次性发送整批SQL到数据库 - 返回`int[]`数组表示每条SQL影响的行数 3. **分段执行**: - 每100条执行一次,避免内存溢出(OOM) - 根据数据库配置调整批次大小(通常500-2000条/批) #### 性能对比 | 插入方式 | 10,000条耗时 | 资源消耗 | |----------------|--------------|----------| | 单条插入 | 15-20秒 | 高 | | 批量插入 | 0.5-2秒 | 低 | | 带事务的批量 | 0.3-1秒 | 最低 | #### 常见问题原因 1. **内存溢出(OutOfMemoryError)**: - 原因:一次性添加过多批处理未及时执行 - 解决:分批执行(如每500条执行一次) 2. **事务超时**: - 原因:单批次数据量过大,执行时间超过数据库等待超时 - 解决:减小批次大小或增加`wait_timeout` 3. **主键冲突**: - 原因:批量操作中部分数据违反唯一约束 - 解决:使用`try-catch`捕获`BatchUpdateException`,通过`getUpdateCounts()`定位失败行 4. **连接泄漏**: - 原因:未正确关闭`PreparedStatement`和`Connection` - 解决:使用try-with-resources语法(Java 7+) #### 高级技巧 ```java // 1. 获取自动生成的主键(需在SQL中指定) PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // 2. 批处理异常处理 try { pstmt.executeBatch(); } catch (BatchUpdateException e) { int[] successCounts = e.getUpdateCounts(); // 获取成功执行的计数 // 处理部分失败的情况 } // 3. 使用重写BatchedStatements(MySQL优化) // 在JDBC URL后添加参数:jdbc:mysql://...?rewriteBatchedStatements=true ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值