Querying with criteria and example
當查詢需要動態(tài)生成時,使用Criteria
可能是更好的選擇。
Basic criteria queries
創(chuàng)建一個簡單的Criteria,只需要一個實體Class,這個實體被稱為root entity:
session.createCriteria(Item.class);
使用addOrder()
方法添加排序功能:
session.createCriteria(User.class).addOrder( Order.asc("lastname") ).addOrder( Order.asc("firstname") );
不通過Session
創(chuàng)建DetachedCriteria
對象,然后再關聯(lián)到Session執(zhí)行:
DetachedCriteria crit = DetachedCriteria.forClass(User.class).addOrder(Order.asc("lastname"))
.addOrder(Order.asc("firstname"));
List result = crit.getExecutableCriteria(session).list();
Applying restrictions
為Criteria
添加約束,需要創(chuàng)建Criterion
對象,Restrictions
類提供了許多工廠方法來創(chuàng)建Criterion對象。
// 創(chuàng)建約束條件: email屬性等于'foo@hibernate.org'
Criterion emailEq = Restrictions.eq("email", "foo@hibernate.org");
Criteria crit = session.createCriteria(User.class);
// 為Criteria添加Criterion約束
crit.add(emailEq);
User user = (User) crit.uniqueResult();
// method chaining
User user = (User) session.createCriteria(User.class)
.add(Restrictions.eq("email", "foo@hibernate.org"))
.uniqueResult();
component property:
session.createCriteria(User.class).add( Restrictions.eq("homeAddress.street", "Foo"));
Creating comparison exrpessions
Restrictions
的各種方法:
// between
Criterion restriction = Restrictions.between("amount", new BigDecimal(100), new BigDecimal(200));
session.createCriteria(Bid.class).add(restriction);
// 大于
session.createCriteria(Bid.class).add(Restrictions.gt("amount", new BigDecimal(100)));
// in
String[] emails = { "foo@hibernate.org", "bar@hibernate.org" };
session.createCriteria(User.class).add(Restrictions.in("email", emails));
// is null
session.createCriteria(User.class).add( Restrictions.isNull("email") );
// is not null
session.createCriteria(User.class).add( Restrictions.isNotNull("email") );
// 集合isEmpty() 或者 isNotEmpty()
session.createCriteria(Item.class).add( Restrictions.isEmpty("bids"));
// 集合大小大于3
session.createCriteria(Item.class).add( Restrictions.sizeGt("bids", 3));
// 比較兩個屬性相等
session.createCriteria(User.class).add( Restrictions.eqProperty("firstname", "username") );
String matching
模糊查詢,可以使用SQL的通配符%, _
,也可以通過指定MatchMode
來實現(xiàn):
// 通配符
session.createCriteria(User.class).add( Restrictions.like("username", "G%") );
// 與上一行等價
session.createCriteria(User.class).add( Restrictions.like("username", "G", MatchMode.START) );
MatchMode
有START, END, ANYWHERE, EXACT
。
字符串大小寫,SQL/HQL可以通過LOWER()
函數(shù)實現(xiàn),Criteria
方式如下:
session.createCriteria(User.class).add( Restrictions.eq("username", "foo").ignoreCase() );
Combining expression with logical operators
邏輯表達:
// firstname like 'G%' AND lastname like 'K%'
session.createCriteria(User.class).add( Restrictions.like("firstname", "G%") ).add( Restrictions.like("lastname", "K%") );
// (firstname like 'G%' AND lastname like 'K%') OR (email in (emails))
session.createCriteria(User.class)
.add(
Restrictions.or(
Restrictions.and(
Restrictions.like("firstname", "G%"),
Restrictions.like("lastname", "K%")
),
Restrictions.in("email", emails)
)
);
Adding arbitrary SQL expressions
在Criteria中直接使用SQL添加restriction(即WHERE子句中):
session.createCriteria(User.class)
.add( Restrictions.sqlRestriction(
"length({alias}.PASSWORD) < ?",
5,
Hibernate.INTEGER
)
);
{alias}
總是指向root entity,本例中即為User。
甚至可以這樣:
session.createCriteria(Item.class)
.add( Restrictions.sqlRestriction(
"'100' > all" +
" ( select b.AMOUNT from BID b" +
" where b.ITEM_ID = {alias}.ITEM_ID )"
)
);
Writing subqueries
A subquery in a criteria query is a WHERE clause subselect.
DetachedCriteria subquery = DetachedCriteria.forClass(Item.class, "i");
subquery.add( Restrictions.eqProperty("i.seller.id", "u.id"))
.add( Restrictions.isNotNull("i.successfulBid") )
.setProjection( Property.forName("i.id").count() );
// 10 < subquery,最終查詢銷售超過10個商品的用戶
Criteria criteria = session.createCriteria(User.class, "u")
.add( Subqueries.lt(10, subquery) );
Joins and dynamic fetching
就像在HQL中使用JOIN
以及dynamically fetch,Criteria
中也可以。
Joining associations for restriction
Item與item.bids集合INNER JOIN:
Criteria itemCriteria = session.createCriteria(Item.class);
itemCriteria.add(Restrictions.like("description","Foo",MatchMode.ANYWHERE));
// 嵌套創(chuàng)建Criteria對象
Criteria bidCriteria = itemCriteria.createCriteria("bids");
bidCriteria.add( Restrictions.gt( "amount", new BigDecimal(99) ) );
List result = itemCriteria.list();
// method chaining
List result = session.createCriteria(Item.class)
.add( Restrictions.like("description","Foo",MatchMode.ANYWHERE))
.createCriteria("bids")
.add( Restrictions.gt("amount", new BigDecimal(99) ) ).list();
Item與seller association INNER JOIN:
List result = session.createCriteria(Item.class)
.createCriteria("seller")
.add( Restrictions.like("email", "%@hibernate.org") ).list();
還可以只創(chuàng)建一個Criteria對象來實現(xiàn):
session.createCriteria(Item.class)
.createAlias("bids", "b") // 創(chuàng)建集合別名
.add( Restrictions.like("description", "%Foo%") )
.add( Restrictions.gt("b.amount", new BigDecimal(99) ) );
// 如果屬性是root entity的可以不添加別名約束(如上的description),或者可以使用this關鍵字
session.createCriteria(Item.class)
.createAlias("bids", "b")
.add( Restrictions.like("this.description", "%Foo%") )
.add( Restrictions.gt("b.amount", new BigDecimal(99) ) );
session.createCriteria(Item.class)
.createAlias("seller", "s")
.add( Restrictions.like("s.email", "%hibernate.org" ) );
Dynamic fetching with criteria queries
Criteria
實現(xiàn)dynamically fetch:
// 返回的Item對象,bids集合屬性被初始化
session.createCriteria(Item.class)
.setFetchMode("bids", FetchMode.JOIN)
.add( Restrictions.like("description", "%Foo%") );
FetchMode.JOIN
生成OUTER JOIN
,如果要生成INNER JOIN
,使用FetchMode.INNER_JOIN
。
session.createCriteria(Item.class)
.createAlias("bids", "b", CriteriaSpecification.INNER_JOIN)
.setFetchMode("b", FetchMode.JOIN)
.add( Restrictions.like("description", "%Foo%") );
The same caveats as in HQL and JPA QL apply here: Eager fetching more than one collection in parallel (such as bids and images) results in an SQL Cartesian product that is probably slower than two separate queries.
Criteria的dynamic fetch 同HQL的不同:
A Criteria query doesn’t ignore the global fetching strategies as defined in the mapping metadata.For example, if the bids collection is mapped with
fetch="join"
orFetchType.EAGER
, the following query results in an outer join of the ITEM and BID table:
// 使用設置的全局抓取策略
session.createCriteria(Item.class).add( Restrictions.like("description", "%Foo%") );
The returned Item instances have their bids collections initialized and fully loaded. This doesn’t happen with HQL or JPA QL unless you manually query with
LEFT JOIN FETCH
(or, of course, map the collection as lazy="false", which results in a second SQL query).
Applying a result transformer
當使用JOIN
后,查詢結果就可能包含很多重復的主表數(shù)據(jù),如何去重呢?
ResultTransformer
的默認實現(xiàn)是Criteria.ROOT_ENTITY
:
List result = session.createCriteria(Item.class)
.setFetchMode("bids", FetchMode.JOIN)
.setResultTransformer(Criteria.ROOT_ENTITY)
.list();
// 利用Set去除重復
Set distinctResult = new LinkedHashSet(result);
使用Criteria.DISTINCT_ROOT_ENTITY
去重:
List distinctResult = session.createCriteria(Item.class)
.setFetchMode("bids", FetchMode.JOIN)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
如果想同時查出關聯(lián)實體對象,使用Criteria.ALIAS_TO_ENTITY_MAP
:
Criteria crit = session.createCriteria(Item.class)
.createAlias("bids", "b")
.createAlias("seller", "s")
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List result = crit.list();
// List中是Map
for (Object aResult : result) {
Map map = (Map) aResult;
Item item = (Item) map.get(Criteria.ROOT_ALIAS);
Bid bid = (Bid) map.get("b");
User seller = (User) map.get("s");
}
HQL和SQL query也支持ResultTransformer
,可將查詢結果封裝為自定義DTO:
Query q = session.createQuery("select i.id as itemId, i.description as desc, i.initialPrice as price from Item i");
q.setResultTransformer( Transformers.aliasToBean(ItemDTO.class) );
Projection and report queries
本小節(jié)介紹Criteria
如何指定要查詢的字段,以及如何使用分組和聚合。
Simple projection lists
如何為Criteria指定一個或多個projection,如何設置字段別名,如何將查詢結果直接封裝成一個對象?注意Projections.property(),Property.forName()這兩種寫法。
// setProjection() 指定一個要查詢的字段(屬性)
session.createCriteria(Item.class)
.add( Restrictions.gt("endDate", new Date()) )
.setProjection( Projections.id() );
// 指定多個要查詢的字段,Projections.property()
// 此查詢返回 a List of Object[]
session.createCriteria(Item.class)
.setProjection( Projections.projectionList()
.add( Projections.id() )
.add( Projections.property("description") )
.add( Projections.property("initialPrice") )
);
// 同上行等價的寫法,使用Property.forName()
session.createCriteria(Item.class)
.setProjection( Projections.projectionList()
.add( Property.forName("id") )
.add( Property.forName("description") )
.add( Property.forName("initialPrice") )
);
// HQL中可以通過SELECT NEW直接查詢出自定義對象的List
// Criteria也可以,使用setResultTransformer(),注意projection使用了別名
session.createCriteria(Item.class)
.setProjection( Projections.projectionList()
.add( Projections.id().as("itemId") )
.add( Projections.property("description").as("itemDescription") )
.add( Projections.property("initialPrice").as("itemInitialPrice") )
).setResultTransformer(
new AliasToBeanResultTransformer(ItemPriceSummary.class)
);
Aggregation and grouping
使用分組和聚合函數(shù),作者在寫此書時(2006),Criteria不支持HAVING:
// rowCount(),查詢出總行數(shù),相當于count(*)
session.createCriteria(Item.class)
.setProjection( Projections.rowCount() );
// 根據(jù)u.id,u.username分組,并統(tǒng)計bid的數(shù)量,計算bid.amount的平均值
// 返回 a collection of Object[],數(shù)組中有四個字段:user identifer,username,number of bids, average bid amount
session.createCriteria(Bid.class)
.createAlias("bidder", "u")
.setProjection( Projections.projectionList()
.add( Property.forName("u.id").group() )
.add( Property.forName("u.username").group() )
.add( Property.forName("id").count())
.add( Property.forName("amount").avg() )
);
// 同上一行等價的寫法
session.createCriteria(Bid.class)
.createAlias("bidder", "u")
.setProjection( Projections.projectionList()
.add( Projections.groupProperty("u.id") )
.add( Projections.groupProperty("u.username") )
.add( Projections.count("id") )
.add( Projections.avg("amount") )
);
// 添加排序
session.createCriteria(Bid.class)
.createAlias("bidder", "u")
.setProjection( Projections.projectionList()
.add( Projections.groupProperty("u.id") )
.add( Projections.groupProperty("u.username").as("uname") )
.add( Projections.count("id") )
.add( Projections.avg("amount") )
)
.addOrder( Order.asc("uname") );
Using SQL projections
添加SQL片斷到最終生成的SELECT子句中:
String sqlFragment = "(select count(*) from ITEM i where i.ITEM_ID = ITEM_ID) as numOfItems";
session.createCriteria(Bid.class)
.createAlias("bidder", "u")
.setProjection( Projections.projectionList()
.add( Projections.groupProperty("u.id") )
.add( Projections.groupProperty("u.username") )
.add( Projections.count("id") )
.add( Projections.avg("amount) )
// 添加SQL片斷,并指定字段名稱及類型
.add( Projections.sqlProjection(sqlFragment, new String[] { "numOfItems" }, new Type[] { Hibernate.LONG }) )
);
生成的SQL:
SELECT u.USER_ID,
u.USERNAME,
count(BID_ID),
avg(BID_AMOUNT),
(SELECT count(*)
FROM ITEM i
WHERE i.ITEM_ID = ITEM_ID) AS numOfItems
FROM BID
INNER JOIN USERS u ON BIDDER_ID = u.USER_ID
GROUP BY u.USER_ID, u.USERNAME
Query by example
QBE,當查詢依賴用戶的輸入時,使用QBE更方便,否則需要拼接SQL/HQL。比如:
// 拼接HQL
public List findUsers(String firstname, String lastname) {
StringBuffer queryString = new StringBuffer();
boolean conditionFound = false;
if (firstname != null) {
queryString.append("lower(u.firstname) like :firstname ");
conditionFound = true;
}
if (lastname != null) {
if (conditionFound)
queryString.append("and ");
queryString.append("lower(u.lastname) like :lastname ");
conditionFound = true;
}
String fromClause = conditionFound ? "from User u where " : "from User u ";
queryString.insert(0, fromClause).append("order by u.username");
Query query = getSession().createQuery(queryString.toString());
if (firstname != null)
query.setString("firstName", '%' + firstname.toLowerCase() + '%');
if (lastname != null)
query.setString("lastName", '%' + lastname.toLowerCase() + '%');
return query.list();
}
// 使用QBC
public List findUsers(String firstname, String lastname) {
Criteria crit = getSession().createCriteria(User.class);
if (firstname != null) {
// ilike() 忽略大小寫
crit.add(Restrictions.ilike("firstname", firstname, MatchMode.ANYWHERE));
}
if (lastname != null) {
crit.add(Restrictions.ilike("lastname", lastname, MatchMode.ANYWHERE));
}
crit.addOrder(Order.asc("username"));
return crit.list();
}
// 使用QBE
public List findUsersByExample(User u) {
Example exampleUser = Example.create(u)
// 字符串類型查詢時忽略大小寫
.ignoreCase()
// 字符串類型查詢模糊
.enableLike(MatchMode.ANYWHERE)
// 指定不包括的屬性
.excludeProperty("password");
// 默認所有的value-typed屬性,不包括主鍵屬性將用于查詢匹配
return getSession().createCriteria(User.class).add(exampleUser).list();
}
使用QBE可以明顯減少代碼量,Example
就是一個Criterion
,所以QBE可以混合QBC。
// QBE混合QBC
public List findUsersByExample(User u) {
Example exampleUser = Example.create(u)
.ignoreCase()
.enableLike(MatchMode.ANYWHERE);
return getSession().createCriteria(User.class)
.add(exampleUser)
.createCriteria("items")
// 為items添加約束
.add(Restrictions.isNull("successfulBid"))
.list();
}
兩個QBE:
public List findUsersByExample(User u, Item i) {
Example exampleUser = Example.create(u).ignoreCase().enableLike(MatchMode.ANYWHERE);
Example exampleItem = Example.create(i).ignoreCase().enableLike(MatchMode.ANYWHERE);
return getSession().createCriteria(User.class)
.add(exampleUser)
.createCriteria("items")
.add(exampleItem)
.list();
}
QBE看著好像比較好用,不過通常列表頁結果都要聯(lián)結多張表來查詢,所以使用SQL可能是更好的選擇,下篇關注在Hibernate中使用SQL。
此文是對《Java Persistence with Hibernate》第15章Criteria部分的歸納。