首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Hibernate 批量插入數據最佳實踐指南

     編輯:Java 相關知識

為什麼需要批量插入優化?

在處理大量數據導入、日誌收集、報表生成等場景時,逐條執行 session.save() 會導致頻繁的數據庫交互、內存壓力激增甚至 OOM。Hibernate 批量操作通過 flush() 和 clear() 機制,可將內存中的持久化對象定期同步到數據庫並釋放 Session 緩存,大幅提升性能。

核心優化策略

  • 設置批處理大小:在 hibernate.cfg.xml 中配置 hibernate.jdbc.batch_size (建議 20-50)
  • 定期 flush & clear:每累積 N 條記錄調用 session.flush() 同步 SQL,session.clear() 清理一級緩存
  • 禁用二級緩存:批量操作期間關閉實體的二級緩存,避免額外開銷
  • 使用無狀態 Session:StatelessSession 無一級緩存,適合純批量導入場景

完整實現代碼示例

1. 實體類 Medicine.java

package com.xqh.model;

public class Medicine {
    private Integer id;
    private String name;
    private double price;
    private String factoryAdd;
    // Getter/Setter 省略...

2. 映射文件 Medicine.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "https://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.xqh.model.Medicine" table="tb_medicine_batch">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name" not-null="true" length="200"/>
        <property name="price" not-null="true"/>
        <property name="factoryAdd" length="200"/>
    </class>
</hibernate-mapping>

3. DAO 層批量保存實現 MedicineDao.java

package com.xqh.dao;
import java.util.List;
import org.hibernate.Session;
import com.xqh.model.Medicine;
import com.xqh.util.HibernateUtil;

public class MedicineDao {
    public void saveMedicines(List<Medicine> medicines) {
        Session session = null;
        if (medicines == null || medicines.isEmpty()) return;
        
        try {
            session = HibernateUtil.getSession();
            session.beginTransaction();
            
            for (int i = 0; i < medicines.size(); i++) {
                session.save(medicines.get(i));
                
                // 關鍵優化:每 10 條刷新並清理緩存
                if (i % 10 == 0) {
                    session.flush();
                    session.clear();
                }
            }
            
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if (session != null) session.getTransaction().rollback();
        } finally {
            HibernateUtil.closeSession(session);
        }
    }
}

4. Hibernate 工具類 HibernateUtil.java

package com.xqh.util;
import org.hibernate.*;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
    private static final SessionFactory factory;
    
    static {
        try {
            Configuration cfg = new Configuration().configure();
            factory = cfg.buildSessionFactory();
        } catch (HibernateException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    public static Session getSession() {
        return factory != null ? factory.openSession() : null;
    }
    
    public static void closeSession(Session session) {
        if (session != null && session.isOpen()) session.close();
    }
    
    public static SessionFactory getSessionFactory() { return factory; }
}

5. 配置文件 hibernate.cfg.xml 關鍵配置

<property name="hibernate.jdbc.batch_size">20</property>
<property name="hibernate.order_inserts">true</property>
<property name="hibernate.order_updates">true</property>
<property name="hibernate.jdbc.batch_versioned_data">true</property>

前端交互實現

Servlet 接收批量數據 SaveMedicine.java

package com.xqh.servlet;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.xqh.dao.MedicineDao;
import com.xqh.model.Medicine;

public class SaveMedicine extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        String[] names = req.getParameterValues("name");
        String[] prices = req.getParameterValues("price");
        String[] adds = req.getParameterValues("factoryAdd");
        
        if (names != null && prices != null && adds != null 
                && names.length == prices.length && names.length == adds.length) {
            
            List<Medicine> list = new ArrayList<>();
            for (int i = 0; i < names.length; i++) {
                Medicine m = new Medicine();
                m.setName(names[i]);
                m.setPrice(Double.parseDouble(prices[i]));
                m.setFactoryAdd(adds[i]);
                list.add(m);
            }
            
            new MedicineDao().saveMedicines(list);
            req.setAttribute("info", "藥品信息批量保存成功!");
        }
        req.getRequestDispatcher("result.jsp").forward(req, resp);
    }
}

動態表單頁面 index.jsp

<%@ page contentType="text/html;charset=GBK" %>
<html><head><title>批量添加藥品信息</title>
<script>
function addRow() {
    document.getElementById('container').innerHTML += 
        document.getElementById('template').innerHTML;
}
function removeRow() {
    var container = document.getElementById('container');
    var html = container.innerHTML;
    var lastRow = html.lastIndexOf('<table');
    if (lastRow > 0) container.innerHTML = html.substring(0, lastRow);
}
function validate(form) {
    for (var i = 0; i < form.elements.length; i++) {
        if (!form.elements[i].value) {
            alert("請填寫完整信息!");
            return false;
        }
    }
    return true;
}
</script></head>
<body onload="addRow()">
<form action="SaveMedicine" method="post" onsubmit="return validate(this)">
<table align="center">
    <tr><td colspan="6" align="center"><h1>批量添加藥品信息</h1></td></tr>
    <tr><td><div id="container"></div></td></tr>
    <tr><td>
        <input type="button" value="添加一行" onclick="addRow()">
        <input type="button" value="減少一行" onclick="removeRow()">
        <input type="submit" value="批量提交到數據庫">
    </td></tr>
</table>
</form>
<div id="template" style="display:none">
<table><tr>
    <td>名稱:</td><td><input name="name" size="13"></td>
    <td>價格:</td><td><input name="price" size="13"></td>
    <td>廠址:</td><td><input name="factoryAdd" size="30"></td>
</tr></table></div>
</body></html>

性能對比與注意事項

方案1萬條耗時內存佔用適用場景
普通 save()~45秒高 (OOM風險)少量數據
flush/clear 優化~8秒低通用批量導入
StatelessSession~5秒極低純導入、無關聯對象
原生 SQL + PreparedStatement~3秒最低極致性能要求

關鍵注意點

  1. ID 生成策略:使用 identity 或 sequence 時,Hibernate 無法批處理 INSERT;改用 enhanced-sequence 或分配器模式
  2. 事務邊界:大批量操作建議分段提交,避免長事務鎖定資源
  3. 異常處理:記錄失敗記錄主鍵,支持斷點續傳
  4. 並發控制:生產環境考慮使用樂觀鎖或分表策略

通過合理配置批處理參數、定期清理 Session 緩存,Hibernate 完全能滿足百萬級數據導入的性能需求。實際項目中建議結合業務特點,在開發階段即進行壓力測試驗證效果。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved