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

Hibernate 通用 BaseDAO 封裝:CRUD、HQL/SQL、Criteria、分頁、存儲過程、ResultSet 轉 Map 完整實現

     編輯:Java 綜合教學

Hibernate 通用 BaseDAO 封裝:CRUD、HQL/SQL、Criteria、分頁、存儲過程、ResultSet 轉 Map 完整實現

在 Spring + Hibernate 體系中,封裝一個功能完備、類型安全的 BaseDao<T> 能極大減少重複樣板代碼。本文提供生產級實現,涵蓋實體操作、多種查詢方式、分頁、存儲過程調用、結果集轉換工具類。

一、 介面定義:BaseDao<T>

public interface BaseDao<T extends Serializable> {
    // 實體操作
    void save(T entity);
    void update(T entity);
    void saveOrUpdate(T entity);
    void delete(T entity);

    // HQL 單值查詢
    Object findUniqueResult(String hql);

    // 原生 SQL 更新
    void updateBySql(String sql);

    // Criteria 查詢
    List findByCriteria(DetachedCriteria detachedCriteria);
    long getCount(DetachedCriteria detachedCriteria);
    List<T> findPage(int pageNum, int pageSize, DetachedCriteria detachedCriteria);

    // 原生 SQL 查詢(返回 List<Map>)
    List<Map<String, Object>> findBySql(String sql);
    Object findUniqueResultBySql(String sql);
    Pagination findPage(int pageNum, int pageSize, String sql);

    // 存儲過程調用
    List<Map<String, Object>> callProcedure(String procName, List<Object> params);
}

二、 實現類:BaseDaoImpl<T> 繼承 HibernateDaoSupport

@Repository
public class BaseDaoImpl<T extends Serializable> extends HibernateDaoSupport implements BaseDao<T> {

    protected Session getCurrentSession() {
        return getHibernateTemplate().getSessionFactory().getCurrentSession();
    }

    @Override public void save(T entity) {
        Session s = getCurrentSession();
        s.save(entity);
        s.flush(); s.evict(entity);  // 同步到數據庫並脫管,避免後續誤更新
    }
    @Override public void update(T entity) {
        Session s = getCurrentSession();
        s.update(entity);
        s.flush(); s.evict(entity);
    }
    @Override public void saveOrUpdate(T entity) {
        Session s = getCurrentSession();
        s.saveOrUpdate(entity);
        s.flush(); s.evict(entity);
    }
    @Override public void delete(T entity) {
        Session s = getCurrentSession();
        s.delete(entity);
        s.flush(); s.evict(entity);
    }

    @Override public Object findUniqueResult(String hql) {
        return getCurrentSession().createQuery(hql).uniqueResult();
    }

    @Override public void updateBySql(final String sql) {
        getSession().doWork(connection -> 
            connection.prepareStatement(sql).executeUpdate()
        );
    }

    @Override public List findByCriteria(DetachedCriteria dc) {
        return dc.getExecutableCriteria(getCurrentSession()).list();
    }

    @Override public long getCount(DetachedCriteria dc) {
        Criteria c = dc.getExecutableCriteria(getCurrentSession());
        Object cnt = c.setProjection(Projections.rowCount()).uniqueResult();
        c.setProjection(null); // 重置投影,避免影響後續查詢
        return Long.valueOf(String.valueOf(cnt));
    }

    @Override public List<T> findPage(int pageNum, int pageSize, DetachedCriteria dc) {
        Criteria c = dc.getExecutableCriteria(getCurrentSession());
        return c.setFirstResult((pageNum - 1) * pageSize)
                .setMaxResults(pageSize)
                .list();
    }

    @Override public List<Map<String, Object>> findBySql(final String sql) {
        final List<Map<String, Object>> result = new ArrayList<>();
        getSession().doWork(connection -> {
            ResultSet rs = connection.prepareStatement(sql).executeQuery();
            result.addAll(RsHelper.rsToList(rs));
        });
        return result;
    }

    @Override public Object findUniqueResultBySql(String sql) {
        return getCurrentSession().createSQLQuery(sql).uniqueResult();
    }

    @Override public Pagination findPage(int pageNum, int pageSize, final String sql) {
        final Pagination page = new Pagination();
        getSession().doWork(connection -> {
            // 1. 總記錄數
            String countSql = "SELECT COUNT(*) FROM (" + sql + ") t";
            ResultSet rs = connection.prepareStatement(countSql).executeQuery();
            page.setTotal(Long.valueOf(RsHelper.getUniqueResult(rs).toString()));

            // 2. 分頁數據 (MySQL 語法,Oracle/PostgreSQL 需調整)
            long offset = (pageNum - 1L) * pageSize;
            String pageSql = "SELECT * FROM (" + sql + ") t LIMIT " + offset + ", " + pageSize;
            page.setRows(RsHelper.rsToList(connection.prepareStatement(pageSql).executeQuery()));
        });
        return page;
    }

    @Override public List<Map<String, Object>> callProcedure(String procName, final List<Object> params) {
        final List<Map<String, Object>> result = new ArrayList<>();
        final StringBuilder sql = new StringBuilder("{call ").append(procName).append("(");
        for (int i = 0; i < (params != null ? params.size() : 0); i++) {
            sql.append("?");
            if (i + 1 < params.size()) sql.append(",");
        }
        sql.append(")}");

        getSession().doWork(connection -> {
            CallableStatement cs = connection.prepareCall(sql.toString());
            if (params != null) {
                for (int i = 0; i < params.size(); i++) {
                    cs.setObject(i + 1, params.get(i));
                }
            }
            result.addAll(RsHelper.rsToList(cs.executeQuery()));
        });
        return result;
    }
}

三、 輔助工具類

1. RsHelper:ResultSet → List<Map> 通用轉換

public class RsHelper {
    public static Object getUniqueResult(ResultSet rs) throws SQLException {
        return rs.next() ? rs.getObject(1) : null;
    }

    public static List<Map<String, Object>> rsToList(ResultSet rs) throws SQLException {
        if (rs == null) return Collections.emptyList();
        ResultSetMetaData md = rs.getMetaData();
        int cols = md.getColumnCount();
        List<Map<String, Object>> list = new ArrayList<>();
        while (rs.next()) {
            Map<String, Object> row = new HashMap<>(cols);
            for (int i = 1; i <= cols; i++) {
                row.put(md.getColumnName(i), rs.getObject(i));
            }
            list.add(row);
        }
        return list;
    }
}

2. Pagination:前端交互分頁對象


public class Pagination {
    private Long total = 0L;
    private List rows = new ArrayList();

    public Long getTotal() { return total; }
    public void setTotal(Long total) { this.total = total; }
    public List getRows() { return rows; }
    public void setRows(List rows) { this.rows = rows; }
}

四、 使用示例

@Service
public class UserService {
    @Autowired private BaseDao userDao;

    public void demo() {
        // 1. 保存
        userDao.save(new User("Alice", 25));

        // 2. HQL 單值
        Long count = (Long) userDao.findUniqueResult("SELECT COUNT(*) FROM User");

        // 3. Criteria 分頁
        DetachedCriteria dc = DetachedCriteria.forClass(User.class)
            .add(Restrictions.eq("age", 25))
            .addOrder(Order.desc("createTime"));
        List pageData = userDao.findPage(1, 20, dc);

        // 4. 原生 SQL 查詢
        List> raw = userDao.findBySql("SELECT id, name FROM t_user WHERE status=1");

        // 5. SQL 分頁 (返回 Pagination)
        Pagination pg = userDao.findPage(1, 20, "SELECT * FROM t_user WHERE status=1");

        // 6. 存儲過程
        List> procResult = userDao.callProcedure("sp_get_user_stats", Arrays.asList(2024, 1));
    }
}

五、 關鍵設計決策與最佳實踐

決策點選擇理由
繼承 HibernateDaoSupport是自動注入 SessionFactory、提供 getHibernateTemplate()/getSession(),簡化代碼
泛型 <T extends Serializable>是實體需實現 Serializable,支持 Session.evict()/merge() 等操作
flush() + evict() 組合保存/更新後執行確保立即同步到 DB,並脫離 Session 管理,防止後續業務邏輯誤觸發更新
doWork() 回調執行原生 SQL是獲取底層 Connection,靈活控制 PreparedStatement/CallableStatement,避免 Hibernate 干擾
SQL 分頁方言差異代碼中用 MySQL LIMIT生產需抽象 DialectPaginationHelper,根據數據庫方言動態拼裝(Oracle ROWNUM、PostgreSQL LIMIT/OFFSET、SQL Server OFFSET/FETCH)
存儲過程返回多結果集僅支持單結果集多結果集需在 doWork 中循环 cs.getMoreResults() 處理,擴展返回 List<List<Map>>
ResultSet 列名大小寫保持數據庫原樣建議統一轉小寫 md.getColumnName(i).toLowerCase(),避免前端 Key 不匹配

六、 進階擴展方向

  • 命名查詢支持:新增 findByNamedQuery(String name, Map<String, Object> params)
  • 批量操作:batchSave/Update/Delete(List<T> entities) 利用 Session.flush()/clear() 定期清理
  • 樂觀鎖輔助:自動檢測 @Version 字段,異常轉換為業務異常
  • 查詢緩存集成:在 Criteria/Query 上啟用 setCacheable(true),配合二級緩存
  • 動態數據源切換:結合 AbstractRoutingDataSource 實現讀寫分離/多租戶

這個 BaseDao 體系在保持簡潔的同時,覆蓋了 90% 以上的持久層常見場景,是中小型項目快速開發的利器。大型項目建議進一步抽象 Repository 接口,配合 Spring Data JPA / MyBatis 共存架構。

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