傳統 DAO 為每個實體類編寫重複的 CRUD 代碼 (save、delete、findById、findAll…),違反 DRY 原則。Hibernate 官方推薦的 Generic DAO 模式利用 JDK 5 泛型,將通用操作提取到基類,具體 DAO 僅需關注特有查詢。
public interface IGenericDAO {
T findById(ID id);
T findById(ID id, boolean lock); // 支持悲觀鎖
List findAll();
List findByExample(T exampleInstance);
List findByExample(T exampleInstance, String[] excludeProperties);
T makePersistent(T entity); // saveOrUpdate
void makeTransient(T entity); // delete
}
public abstract class GenericDAOHibernate>
implements IGenericDAO {
private Class persistentClass;
protected Session session;
@SuppressWarnings("unchecked")
public GenericDAOHibernate() {
// 通過反射獲取泛型實際類型參數
this.persistentClass = (Class) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public DAOImpl setSession(Session s) {
this.session = s;
return (DAOImpl) this; // 支持鏈式調用
}
protected Session getSession() {
if (session == null) throw new IllegalStateException("Session 未設置");
return session;
}
// ---------- 通用 CRUD 實現 ----------
@Override @SuppressWarnings("unchecked")
public T findById(ID id) {
return (T) getSession().load(getPersistentClass(), id);
}
@Override @SuppressWarnings("unchecked")
public T findById(ID id, boolean lock) {
if (lock) return (T) getSession().load(getPersistentClass(), id, LockMode.UPGRADE);
return findById(id);
}
@Override @SuppressWarnings("unchecked")
public List findAll() {
return findByCriteria();
}
@Override @SuppressWarnings("unchecked")
public List findByExample(T exampleInstance) {
Criteria crit = getSession().createCriteria(getPersistentClass());
crit.add(Example.create(exampleInstance));
return crit.list();
}
@Override @SuppressWarnings("unchecked")
public List findByExample(T exampleInstance, String[] excludeProperties) {
Criteria crit = getSession().createCriteria(getPersistentClass());
Example example = Example.create(exampleInstance);
for (String prop : excludeProperties) example.excludeProperty(prop);
crit.add(example);
return crit.list();
}
@Override @SuppressWarnings("unchecked")
public T makePersistent(T entity) {
getSession().saveOrUpdate(entity);
return entity;
}
@Override
public void makeTransient(T entity) {
getSession().delete(entity);
}
// ---------- 靈活的 Criteria 封裝 ----------
@SuppressWarnings("unchecked")
protected List findByCriteria(Criterion... criterions) {
Criteria crit = getSession().createCriteria(getPersistentClass());
for (Criterion c : criterions) crit.add(c);
return crit.list();
}
@SuppressWarnings("unchecked")
protected List findByCriteria(Order order, Criterion... criterions) {
Criteria crit = getSession().createCriteria(getPersistentClass());
for (Criterion c : criterions) crit.add(c);
if (order != null) crit.addOrder(order);
return crit.list();
}
@SuppressWarnings("unchecked")
protected List findByCriteria(int firstResult, int maxResults, Order order, Criterion... criterions) {
Criteria crit = getSession().createCriteria(getPersistentClass());
for (Criterion c : criterions) crit.add(c);
if (order != null) crit.addOrder(order);
crit.setFirstResult(firstResult);
crit.setMaxResults(maxResults);
return crit.list();
}
public Class getPersistentClass() { return persistentClass; }
}
// 1. 定義擴展接口
public interface IUserDAO extends IGenericDAO {
User findByUsername(String username);
User findByUsernameAndPassword(String username, String password);
}
// 2. 實現類繼承基類
public class UserDAOHibernate extends GenericDAOHibernate implements IUserDAO {
@Override
public User findByUsername(String username) {
return findByCriteria(Restrictions.eq("username", username)).stream().findFirst().orElse(null);
}
@Override
public User findByUsernameAndPassword(String username, String password) {
return findByCriteria(
Restrictions.eq("username", username),
Restrictions.eq("password", password)
).stream().findFirst().orElse(null);
}
}
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">...</bean>
<bean id="userDAO" class="com.example.dao.UserDAOHibernate">
<property name="session" ref="sessionFactory" /> <!-- 注入 Session 或用 SessionFactory.getCurrentSession() -->
</bean>
| 優點 | 缺點/注意 |
|---|---|
| 消除 80% 樣板代碼 | 泛型擦除導致運行時需反射獲取實體類,微小性能損耗 |
| 統一異常處理、事務邊界 | 複雜查詢仍需在子類寫 Criteria/HQL/原生 SQL |
| 易於單元測試 (Mock Session) | Hibernate 5/6 API 變化需適配 (如 SessionFactory/Session 管理) |
| 支持鎖、分頁、排序、Example 查詢 | 現代項目多改用 Spring Data JPA (Repository 接口自動實現) |
public interface UserRepository extends JpaRepository {
User findByUsername(String username);
User findByUsernameAndPassword(String username, String password);
// 無需實現類,Spring 自動代理實現
}
Generic DAO 是 Hibernate 時代經典設計模式,展示了泛型、模板方法、反射、Criteria API的綜合運用。雖然 Spring Data JPA 已成為主流,但理解 Generic DAO 原理有助於閱讀舊代碼、自定義 Repository 基類、排查 ORM 底層行為。核心思想——將不變的 CRUD 抽象到基類,將變的業務查詢留給子類——永不過時。