使用 entityManager.persist() 循環插入,即使配合 flush() 和 clear(),Hibernate 仍會生成 N 條 INSERT 語句,網絡往返開銷大,性能低下。
@PersistenceContext
EntityManager em;
public void batchInsertNative(List entities) {
StringBuilder sql = new StringBuilder("INSERT INTO my_table (col1, col2) VALUES ");
for (int i = 0; i < entities.size(); i++) {
if (i > 0) sql.append(", ");
sql.append("(?, ?)");
}
Query query = em.createNativeQuery(sql.toString());
for (int i = 0; i < entities.size(); i++) {
Entity e = entities.get(i);
query.setParameter(i * 2 + 1, e.getCol1());
query.setParameter(i * 2 + 2, e.getCol2());
}
query.executeUpdate();
}
優點:單條 SQL,性能最強。缺點:失去 ORM 映射優勢,需手動維護字段映射。
1. 配置批處理大小 (application.yml)
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
2. 實體類需使用原生 ID 生成策略
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 或 SEQUENCE,避免 TABLE
private Long id;
3. 代碼實現
@Transactional
public void batchInsertJpa(List entities) {
for (int i = 0; i < entities.size(); i++) {
entityManager.persist(entities.get(i));
if (i % 50 == 0) { // 與 batch_size 一致
entityManager.flush();
entityManager.clear();
}
}
}
此時 Hibernate 會將累積的 INSERT 合並為 JDBC 批處理發送,大幅減少網絡往返。
saveAll()(簡單但有陷阱)repository.saveAll(entities); // 內部仍是循環 persist,需配合上述配置才能觸發批處理
| 方案 | 耗時(ms) | SQL 條數 | 備註 |
|---|---|---|---|
| 循環 persist 無優化 | ~12000 | 10000 | 極慢 |
| saveAll + 批處理配置 | ~800 | ~200 | 推薦 |
| 原生 SQL 批量插入 | ~300 | 1 | 最快,但維護成本高 |
COPY / LOAD DATA。clear() 避免 OOM。rewriteBatchedStatements=true 連接參數進一步優化。