ITEEDU

8.3. Querying

如果你不能确定你要寻找的对象的标示符,请使用Session的find()方法。Hibernate使用一种简单而强大的面向对象查询语言。

List cats = sess.find(
    "from Cat as cat where cat.birthdate = ?",
    date,
    Hibernate.DATE
);

List mates = sess.find(
    "select mate from Cat as cat join cat.mate as mate " +
    "where cat.name = ?",
    name,
    Hibernate.STRING
);

List cats = sess.find( "from Cat as cat where cat.mate.bithdate is null" );

List moreCats = sess.find(
    "from Cat as cat where " + 
    "cat.name = 'Fritz' or cat.id = ? or cat.id = ?",
    new Object[] { id1, id2 },
    new Type[] { Hibernate.LONG, Hibernate.LONG }
);

List mates = sess.find(
    "from Cat as cat where cat.mate = ?",
    izi,
    Hibernate.entity(Cat.class)
);

List problems = sess.find(
    "from GoldFish as fish " +
    "where fish.birthday > fish.deceased or fish.birthday is null"
);

find()的第二个参数接受一个对象或者对象数组。第三个参数接受一个Hibernate类型或者类型的数组。这些指定的类型用来把给定的对象绑定到查询中的?占位符(实际上对应的是JDBC PreparedStatement的传入参数)。就像在JDBC中一眼,你应该优先使用这种参数绑定的方式,而非组装字符串。

Hibernate类定义了一些静态方法和常量,提供了访问大部分内置类型的手段。这些内置类型是net.sf.hibernate.type.Type的实例。

如果你知道你的查询会返回非常大量的对象,但是你不希望全部使用它们,你可以用iterate()方法获得更好的性能,它会返回一个java.util.Iterator。这个迭代器会在需要的时候装载对象,所使用的标识符来自一个前导的SQL查询。(一共是N+1次查询)

// fetch ids
Iterator iter = sess.iterate("from eg.Qux q order by q.likeliness"); 
while ( iter.hasNext() ) {
    Qux qux = (Qux) iter.next();  // fetch the object
    // something we couldnt express in the query
    if ( qux.calculateComplicatedAlgorithm() ) {
        // delete the current instance
        iter.remove();
        // dont need to process the rest
        break;
    }
}

很不幸,java.util.Iterator没有声明任何exception。所以,发生的任何SQL或者Hibernate的exception都会被包装在一个LazyInitializationException中(它是RuntimeException的子类)。

如果你预期大部分的对象已经装载过,存在于session的缓存中了,或者查询结果包含同样的对象很多次,那么iterator()方法也会获得更好的性能。(如果没有任何数据被缓存或者重复出现,则find()总是会更快。)下面是一个应该使用iterator()调用的查询例子:

Iterator iter = sess.iterate(
    "select customer, product " + 
    "from Customer customer, " +
    "Product product " +
    "join customer.purchases purchase " +
    "where product = purchase.product"
);

如果对上面的查询使用find(),会返回一个非常大的JDBCResultSet,包含很多重复的相同数据。

有时候Hibernate查询会每行返回多种对象,这种情况下,每行会返回一个数组,包含多个对象元素:

Iterator foosAndBars = sess.iterate(
    "select foo, bar from Foo foo, Bar bar " +
    "where bar.date = foo.date"
);
while ( foosAndBars.hasNext() ) {
    Object[] tuple = (Object[]) foosAndBars.next();
    Foo foo = tuple[0]; Bar bar = tuple[1];
    ....
}

8.3.1. 标量查询(Scalar query)

查询可以在select子句中指定类的属性。甚至可以调用SQL的统计函数。属性或者统计值被称为“标量(scalar)”结果。

Iterator results = sess.iterate(
        "select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
        "group by cat.color"
);
while ( results.hasNext() ) {
    Object[] row = results.next();
    Color type = (Color) row[0];
    Date oldest = (Date) row[1];
    Integer count = (Integer) row[2];
    .....
}
Iterator iter = sess.iterate(
    "select cat.type, cat.birthdate, cat.name from DomesticCat cat"
);
List list = sess.find(
    "select cat, cat.mate.name from DomesticCat cat"
);

8.3.2. 查询接口(Query interface)

如果你需要为你的结果集设置边界(你需要获取的最大行数与/或你希望获取的第一行),你应该得到一个net.sf.hibernate.Query的实例:

Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();

你甚至可以在映射文档中定义命名查询。(记得用一个CDATA块把你的查询包含起来,否则在分析的时候可能引起误解。)

<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
    from eg.DomesticCat as cat
        where cat.name = ?
        and cat.weight > ?
] ]></query>
Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();

查询界面支持使用命名参数。命名参数用:name的形式在查询字符串中表示。在Query中有方法把实际参数绑定到命名参数或者JDBC风格的?参数。 和JDBC不同,Hibernate的参数从0开始计数。 使用命名参数有一些好处:

  • 命名参数不依赖于它们在查询字符串中出现的顺序

  • 在同一个查询中可以使用多次

  • 他们可读性好

//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//positional parameter
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();

8.3.3. 可滚动迭代(Scrollable iteration)

如果你的JDBC驱动支持可滚动的ResuleSet,Query接口可以获取一个ScrollableResults,允许你在查询结果中灵活游走。

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
                            "order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {

    // find the first name on each page of an alphabetical list of cats by name
    firstNamesOfPages = new ArrayList();
    do {
        String name = cats.getString(0);
        firstNamesOfPages.add(name);
    }
    while ( cats.scroll(PAGE_SIZE) );

    // Now get the first page of cats
    pageOfCats = new ArrayList();
    cats.beforeFirst();
    int i=0;
    while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );

}

scroll()的行为方式与iterate()很类似,除了对象可以有选择的用get(int)初始化,而非整个行都一次性被初始化。

8.3.4. 过滤集合类(Filtering collections)

集合filter是一种特殊的查询,用于一个持久化集合或者数组。查询字符串可以引用this,意为当前的数组元素。

Collection blackKittens = session.filter( 
    pk.getKittens(), "where this.color = ?", Color.BLACK, Hibernate.enum(Color.class)
);

返回的集合被认为是一个包(bag)。

请注意filter并不需要from 子句(当然需要的话它们也可以加上)。Filter不限定返回它们自己的集合元素。

Collection blackKittenMates = session.filter( 
    pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK"
);

8.3.5. 条件查询

HQL极为强大,但是有些人希望能够动态的使用一种面向对象API创建查询,而非在他们的Java代码中嵌入字符串。对于那部分人来说,Hibernate提供了一种直观的Criteria查询API。

Criteria crit = session.createCriteria(Cat.class);
crit.add( Expression.eq("color", eg.Color.BLACK) );
crit.setMaxResults(10);
List cats = crit.list();

如果你对类似于SQL的语法不是感觉很舒服的话,用这种方法开始使用Hibernate可能更容易。这种API也比HQL更可扩展。程序可以提供它们自己的Criterion接口的实现。

8.3.6. 使用本地SQL的查询

你可以使用createSQLQuery()方法,用SQL来表达查询。你必须把SQL别名用大括号包围起来。

List cats = session.createSQLQuery(
    "SELECT {cat.*} FROM CAT AS {cat} WHERE ROWNUM<10", 
    "cat",
    Cat.class
).list();
List cats = session.createSQLQuery(
    "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, {cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
    "FROM CAT AS {cat} WHERE ROWNUM<10", 
    "cat",
    Cat.class
).list()

和Hibernate查询一样,SQL查询也可以包含命名参数或者顺序参数。