單條 INSERT 頻繁交互數據庫是典型性能殺手。本文通過完整案例演示如何使用 JDBC Statement.addBatch() / executeBatch() 實現批量寫入,並解決 MySQL 下序列模擬、事務控制、異常處理等工程化細節。
向 Emp 表批量插入 100 條記錄:
empno:由序列 emp_seq 自動生成ename:"name" + 循環序號sal:10000 以內隨機整數Connection,一次網絡往返發送多條 SQLsetAutoCommit(false)),手動 commit()/rollback()MySQL 無原生 SEQUENCE (8.0+ 引入),採用單行表 + LAST_INSERT_ID() 模擬:
CREATE TABLE emp_seq (seq BIGINT NOT NULL);
INSERT INTO emp_seq VALUES (0);
-- 獲取下一值 (原子操作,線程安全)
UPDATE emp_seq SET seq = LAST_INSERT_ID(seq + 1);
SELECT LAST_INSERT_ID(); -- 返回新值
關鍵點:LAST_INSERT_ID(expr) 將函數返回值設為 expr,並作為該連接的「最後插入 ID」,後續 SELECT LAST_INSERT_ID() 即可獲取,不受並發影響。
public class BatchInsertDemo {
public void batchAdd() {
Connection con = null;
Statement stmt = null;
try {
con = ConnectionSource.getConnection(); // 你的連接池獲取邏輯
stmt = con.createStatement();
con.setAutoCommit(false); // 關鍵:開啟事務
for (int i = 0; i < 100; i++) {
// 注意:MySQL 不支持 emp_seq.nextval,需先 UPDATE 再 SELECT,或在 SQL 中嵌套子查詢
// 此處演示邏輯,實際建議使用 PreparedStatement 批處理 (見下文)
String sql = "INSERT INTO emp(empno, ename, sal) VALUES (" +
"(UPDATE emp_seq SET seq = LAST_INSERT_ID(seq+1), LAST_INSERT_ID()), " + // 寫法需調整
"'name" + i + "', " +
new Random().nextInt(10000) + ")";
stmt.addBatch(sql);
}
int[] results = stmt.executeBatch(); // 返回每條影響行數數組
con.commit(); // 提交事務
System.out.println("批量插入成功,共 " + results.length + " 條");
} catch (SQLException e) {
try { if (con != null) con.rollback(); } catch (SQLException ex) { ex.printStackTrace(); }
throw new RuntimeException("批量插入失敗", e);
} finally {
closeQuietly(stmt);
closeQuietly(con);
}
}
}
上述代碼中 emp_seq.nextval 是 Oracle 語法,**MySQL 不支持**。實際工程中有兩種做法:
CREATE SEQUENCE;舊版本用存儲過程封裝 UPDATE ... LAST_INSERT_ID() 邏輯,Java 調用 {CALL next_val('emp_seq')}。String sql = "INSERT INTO emp(empno, ename, sal) VALUES (?, ?, ?)";
try (Connection con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
con.setAutoCommit(false);
for (int i = 0; i < 100; i++) {
long id = idGenerator.nextId(); // 應用層生成
ps.setLong(1, id);
ps.setString(2, "name" + i);
ps.setInt(3, new Random().nextInt(10000));
ps.addBatch();
}
ps.executeBatch();
con.commit();
}
啟用 rewriteBatchedStatements=true (Connector/J 5.1.13+),驅動會將批量 INSERT 重寫為單條多值 INSERT INTO ... VALUES (...), (...), ...,極大減少網絡包與解析開銷:
jdbc:mysql://host:3306/db?rewriteBatchedStatements=true&useServerPrepStmts=true
addBatch() 緩存 → executeBatch() 一次性發送 → 事務提交。rewriteBatchedStatements=true。