userRepository.save(user) 更新 6000 條 → **4-5 分鐘**,極慢。EntityManager.merge() + 手動 flush()/clear() → **空指針異常 (NPE)**:em 為 null。// ❌ 錯誤:私有字段無注解,Spring 不會注入
private EntityManager em;
// ✅ 正確:標準 JPA 注入方式
@PersistenceContext
private EntityManager em;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@PersistenceContext
private EntityManager em;
@Override
public void updateUsers(List<User> users) {
int batchSize = 30; // 與 Hibernate jdbc.batch_size 一致
for (int i = 0; i < users.size(); i++) {
em.merge(users.get(i)); // 合併托管狀態
// 定期 flush + clear,釋放一級緩存,防止 OOM
if (i % batchSize == 0 && i > 0) {
em.flush(); // 同步到數據庫
em.clear(); // 清空持久化上下文
}
}
// 最後剩餘數據 flush
em.flush();
}
}
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 30 # 批量發送 SQL 數量
batch_versioned_data: true # 允許帶版本號實體批量更新
order_inserts: true # 按實體類型排序 INSERT,提升批次合並率
order_updates: true # 同上針對 UPDATE
generate_statistics: false # 生產關閉統計,提升性能
| 方法 | 機制 | 性能特點 |
|---|---|---|
repository.save() | 每次調用觸發 EntityManager.persist/merge + 默認 flush | 頻繁交互、大量上下文快照比較 |
em.merge() + 手動 flush/clear | 批量累積 SQL,定期同步,及時清理上下文 | **減少數據庫交互 90%+,內存佔用恆定** |
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id IN :ids")
int batchUpdateName(@Param("name") String name, @Param("ids") List<Long> ids);
@Autowired EntityManager → **錯誤**,需 @PersistenceContext。em.clear() → 一級緩存累積導致 **OOM**。@Version 卻開啟 batch_versioned_data → 異常。EntityManager **必加 @PersistenceContext**。hibernate.jdbc.batch_size 與代碼中 batchSize 保持一致。@Modifying @Query 直寫 SQL,但需權衡 JPA 特性損失。從「幾分鐘」到「幾秒」,只差一個 @PersistenceContext 和正確的批量提交節奏。