在 Spring + Hibernate 體系中,封裝一個功能完備、類型安全的 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);
}
@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;
}
}
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;
}
}
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
| 決策點 | 選擇 | 理由 |
|---|---|---|
| 繼承 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 字段,異常轉換為業務異常setCacheable(true),配合二級緩存AbstractRoutingDataSource 實現讀寫分離/多租戶這個 BaseDao 體系在保持簡潔的同時,覆蓋了 90% 以上的持久層常見場景,是中小型項目快速開發的利器。大型項目建議進一步抽象 Repository 接口,配合 Spring Data JPA / MyBatis 共存架構。