Hiberante查詢之Criteria

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) );

MatchModeSTART, 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 fetchCriteria中也可以。

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"or FetchType.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部分的歸納。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容