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

Spring HibernateTemplate 實作:批量刪除與分頁查詢最佳實踐

     編輯:Java 相關知識

背景與定位

`HibernateTemplate` 是 Spring 早期提供的 Hibernate 存取模板類,封裝了 Session 管理、異常轉換、資源釋放等樣板代碼。雖然 Spring 3.0+ 推薦直接使用 `SessionFactory.getCurrentSession()` 配合 `@Transactional`,但大量存量專案仍在使用 `HibernateTemplate`。本文針對其兩大常見操作——批量刪除與分頁查詢——提供最佳實踐範例。

批量刪除:使用 HQL 參數綁定列表

核心代碼

public int delNews(final List<Long> arrId) {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback<Integer>() {
            @Override
            public Integer doInHibernate(Session session) throws HibernateException, SQLException {
                Query query = session.createQuery(
                    "DELETE FROM News WHERE arrid IN (:ids)"
                );
                query.setParameterList("ids", arrId);
                return query.executeUpdate();
            }
        }
    )).intValue();
}

關鍵技術點解析

  1. HibernateCallback 回調: 取得原生 `Session`,執行複雜 HQL/DML 操作
  2. 命名參數 `:ids`: 避免 SQL 注入,程式碼可讀性高
  3. setParameterList: 專為 `IN` 子句設計,自動展開列表為多個參數 (`?, ?, ?`),支援任意長度列表
  4. executeUpdate(): 執行 DML (UPDATE/DELETE),回傳影響行數
  5. 型別安全: `List<Long>` 確保 ID 型別正確

效能優化建議

  • 大量 ID 分批處理: Oracle `IN` 子句限制 1000 個表達式,超過需分批 (如每批 500/1000)
  • 避免載入實體再刪除: 直接 HQL DELETE 比 `load()` + `delete()` 快數量級 (省去 SELECT + 物件實例化)
  • 考慮原生 SQL: 極大量刪除 (百萬級) 可用 `session.createSQLQuery("DELETE FROM news WHERE arrid IN ...")` 繞過 Hibernate 快取

分批刪除工具方法

public int batchDelete(List<Long> ids, int batchSize) {
    if (ids == null || ids.isEmpty()) return 0;
    
    int totalDeleted = 0;
    for (int i = 0; i < ids.size(); i += batchSize) {
        List<Long> batch = ids.subList(i, Math.min(i + batchSize, ids.size()));
        totalDeleted += deleteBatch(batch);
    }
    return totalDeleted;
}

private int deleteBatch(List<Long> batch) {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback<Integer>() {
            @Override
            public Integer doInHibernate(Session session) {
                Query q = session.createQuery("DELETE FROM News WHERE arrid IN (:ids)");
                q.setParameterList("ids", batch);
                return q.executeUpdate();
            }
        }
    )).intValue();
}

分頁查詢:HQL + setMaxResults

核心代碼

public List<News> getNewsList(final int pageSize) {
    return (List<News>) getHibernateTemplate().execute(
        new HibernateCallback<List<News>>() {
            @Override
            public List<News> doInHibernate(Session session) throws HibernateException, SQLException {
                String hql = "FROM News n ORDER BY n.id DESC";
                Query query = session.createQuery(hql);
                query.setMaxResults(pageSize);  // 限制回傳筆數
                List<News> list = query.list();
                return (list != null && !list.isEmpty()) ? list : null;
            }
        }
    );
}

關鍵技術點解析

  1. ORDER BY n.id DESC: 確保結果順序確定性 (最新優先)
  2. setMaxResults(pageSize): 對應 SQL `LIMIT`/`ROWNUM`/`TOP`,資料庫層面截取,避免全量載入
  3. 空列表防護: 回傳 `null` 而非空列表,視業務語義決定 (建議統一回傳空列表 `Collections.emptyList()` 避免 NPE)

真正的分頁:firstResult + maxResults

public List<News> getNewsPage(final int pageNo, final int pageSize) {
    return (List<News>) getHibernateTemplate().execute(
        new HibernateCallback<List<News>>() {
            @Override
            public List<News> doInHibernate(Session session) {
                Query query = session.createQuery("FROM News n ORDER BY n.id DESC");
                query.setFirstResult((pageNo - 1) * pageSize);  // 起始位移 (0-based)
                query.setMaxResults(pageSize);                    // 每頁筆數
                return query.list();
            }
        }
    );
}

總筆數查詢 (分頁必要資訊)

public long getNewsCount() {
    return ((Long) getHibernateTemplate().execute(
        new HibernateCallback<Long>() {
            @Override
            public Long doInHibernate(Session session) {
                return (Long) session.createQuery("SELECT COUNT(*) FROM News").uniqueResult();
            }
        }
    )).longValue();
}

現代化替代方案:Spring Data JPA / SessionFactory 直用

Spring Data JPA (推薦新專案)

public interface NewsRepository extends JpaRepository<News, Long> {
    // 方法名自動生成查詢
    List<News> findTop10ByOrderByIdDesc();  // 等同 setMaxResults(10)
    
    // 分頁查詢
    Page<News> findAllByOrderByIdDesc(Pageable pageable);
    
    // 批量刪除
    @Modifying
    @Query("DELETE FROM News n WHERE n.arrid IN :ids")
    int deleteByArrIdIn(@Param("ids") List<Long> ids);
}

直接使用 SessionFactory (Spring 3.0+ 推薦)

@Repository
public class NewsDao {
    @Autowired private SessionFactory sessionFactory;

    public int deleteByIds(List<Long> ids) {
        Session session = sessionFactory.getCurrentSession();
        Query query = session.createQuery("DELETE FROM News WHERE arrid IN (:ids)");
        query.setParameterList("ids", ids);
        return query.executeUpdate();
    }

    public List<News> findLatest(int limit) {
        Session session = sessionFactory.getCurrentSession();
        Query query = session.createQuery("FROM News n ORDER BY n.id DESC");
        query.setMaxResults(limit);
        return query.list();
    }
}

常見坑與避雷指南

| 坑點 | 後果 | 解決方案 | |------|------|----------| | `IN` 列表過長 | Oracle ORA-01795、MySQL 解析錯誤 | 分批處理 (建議 ≤ 500) | | 刪除後快取未清 | 二級快取髒讀 | `sessionFactory.getCache().evictEntityRegion(News.class)` 或 `@CacheEvict` | | 分頁無 ORDER BY | 結果順序不穩定,**每頁資料不同** | **必須**加 `ORDER BY` 唯一欄位 (如 PK) | | `setFirstResult` 無索引支援 | 深分頁效能指數級下降 | 避免深分頁,改用游標/Keyset 分頁 (`WHERE id < lastId ORDER BY id DESC LIMIT n`) | | 回傳 null 而非空列表 | 呼叫端 NPE | 統一回傳 `Collections.emptyList()` |

總結

`HibernateTemplate` 時代的批量刪除與分頁查詢核心在於:

  1. 使用 `HibernateCallback` 取得原生 `Session` 執行 HQL
  2. 批量刪除:`DELETE HQL` + `setParameterList` + 分批規避資料庫限制
  3. 分頁查詢:`ORDER BY` 確定性 + `setFirstResult`/`setMaxResults` 資料庫層面截取
  4. 新專案強烈建議遷移至 Spring Data JPA 或直接使用 `SessionFactory.getCurrentSession()`,代碼更簡潔、型別安全、易測試
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved