HQL/JPQL 靜態字串難以應對「使用者任意組合查詢條件」的場景。Criteria API 提供型別安全、物件導向、可程式化組裝的查詢建構方式,極適合動態報表、進階搜尋、通用 DAO 等需求。雖然 Hibernate 5.2+ 推薦 JPA Criteria,但傳統 org.hibernate.Criteria 仍廣泛用於存量專案,本文兩者並陳說明。
| 類別/介面 | 用途 | 建立方式 |
|---|---|---|
Criteria | 線上查詢,需 Session | session.createCriteria(Entity.class) |
DetachedCriteria | 離線查詢,可序列化跨層傳遞 | DetachedCriteria.forClass(Entity.class) |
Restrictions | Criterion 工廠 (eq, like, between, in...) | 靜態方法 |
Projections | Projection 工廠 (count, sum, groupProperty...) | 靜態方法 |
Order | 排序 (asc/desc) | Order.asc("prop") |
Example | QBE (Query By Example) | Example.create(entity) |
Criteria c = session.createCriteria(Product.class);
c.add(Restrictions.like("name", "iPhone%"));
c.addOrder(Order.desc("price"));
c.setFirstResult(0).setMaxResults(20);
List list = c.list();
// WHERE (name LIKE 'iPhone%' OR brand='Apple') AND price > 5000
Criterion nameCond = Restrictions.like("name", "iPhone%");
Criterion brandCond = Restrictions.eq("brand", "Apple");
Criterion priceCond = Restrictions.gt("price", 5000);
c.add(Restrictions.and(priceCond, Restrictions.or(nameCond, brandCond)));
// 更複雜用 Conjunction/Disjunction
Disjunction or = Restrictions.disjunction()
.add(Restrictions.isNull("discount"))
.add(Restrictions.eq("discount", 0));
c.add(Restrictions.and(priceCond, or));
// 內連結 Category,並對關聯表加條件
c.createCriteria("category", "cat") // 回傳新 Criteria 指向 Category
.add(Restrictions.eq("cat.status", 1));
// 只想加條件不想產生新 Criteria,用 createAlias
c.createAlias("category", "cat")
.createAlias("supplier", "sup")
.add(Restrictions.eqProperty("cat.id", "sup.defaultCategoryId"));
c.setFetchMode("category", FetchMode.JOIN) // 左外連結一次撈回
.setFetchMode("tags", FetchMode.SELECT); // 另發 SQL (預設)
// SELECT COUNT(*), AVG(price), MAX(price), color FROM Product GROUP BY color
ProjectionList pl = Projections.projectionList()
.add(Projections.rowCount(), "cnt")
.add(Projections.avg("price"), "avgPrice")
.add(Projections.max("price"), "maxPrice")
.add(Projections.groupProperty("color"), "color");
c.setProjection(pl);
List
Product probe = new Product();
probe.setBrand("Apple");
probe.setStatus(1);
Example ex = Example.create(probe)
.excludeZeroes() // 忽略數值 0
.ignoreCase() // 字串不分大小寫
.enableLike(MatchMode.ANYWHERE); // %value%
c.add(ex);
// 子查詢:找出價格高於「同品牌平均價」的產品
DetachedCriteria avgSub = DetachedCriteria.forClass(Product.class, "p2")
.setProjection(Projections.avg("p2.price"))
.add(Property.forName("p2.brand").eqProperty("p1.brand"));
Criteria main = session.createCriteria(Product.class, "p1");
main.add(Property.forName("p1.price").gt(avgSub));
@Repository
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {
public List findByDetached(DetachedCriteria dc, int offset, int limit) {
return getHibernateTemplate().execute(session -> {
Criteria c = dc.getExecutableCriteria(session);
c.setFirstResult(offset).setMaxResults(limit);
return c.list();
});
}
}
setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) 或分兩次查。setProjection 會改變回傳型別為 Object[] 或 Map,需用 setResultTransformer(Transformers.aliasToBean(DTO.class)) 映射 DTO。list():改用 scroll() 或 iterate() 配合分頁,避免 OOM。Criteria API 是 Hibernate 最強大的動態查詢工具。掌握 Restrictions 條件組合、Projections 聚合分組、createAlias 關聯控制、DetachedCriteria 跨層傳遞與子查詢,即可應付 90% 以上的複雜查詢需求,且程式碼可讀、可測、可維護。