10.4. 查询
如 果不知道所要寻找的对象的持久化标识,那么你需要使用查询。Hibernate 支持强大且易于使用的面向对象查询语言(HQL)。如果希望通过编程的方式创建查询,Hibernate 提供了完善的按条件(Query By Criteria,QBC)以及按样例(Query By Example,QBE)进行查询的功能。你也可以用原生 SQL(native SQL)描述查询,Hibernate 额外提供了将结果集(result set)转化为对象的支持。
10.4.1. 执行查询
HQL 和原生 SQL(native SQL)查询要通过为
org.hibernate.Query
的实例来表达。 这个接口提供了参数绑定、结果集处理以及运行实际查询的方法。你总是可以通过当前 Session
获取一个 Query
对象:
List cats = session.createQuery(
"from Cat as cat where cat.birthdate < ?")
.setDate(0, date)
.list();
List mothers = session.createQuery(
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
List kittens = session.createQuery(
"from Cat as cat where cat.mother = ?")
.setEntity(0, pk)
.list();
Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
一个查询通常在调用
list()
时被执行,执行结果会完全装载进内存中的一个集合(collection)。查询返回的对象处于持久(persistent)状态。如果你知道的查询只会返回一个对象,可使用 list()
的快捷方式 uniqueResult()
。注意,使用集合预先抓取的查询往往会返回多次根对象(他们的集合类都被初始化了)。你可以通过一个集合(Set)
来过滤这些重复对象。
10.4.1.1. 迭代式获取结果(Iterating results)
某些情况下,你可以使用
iterate()
方法得到更好的性能。 这通常是你预期返回的结果在 session,或二级缓存(second-level cache)中已经存在时的情况。如若不然,iterate()
会比 list()
慢,而且可能简单查询也需要进行多次数据库访问:iterate()
会首先使用 1 条语句得到所有对象的持久化标识(identifiers),再根据持久化标识执行 n 条附加的 select 语句实例化实际的对象。
// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
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;
}
}
10.4.1.2. 返回元组(tuples)的查询
(译注:元组(tuples)指一条结果行包含多个对象) Hibernate 查询有时返回元组(tuples),每个元组(tuples)以数组的形式返回:
Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
....
}
10.4.1.3. 标量(Scalar)结果
查询可在
select
从句中指定类的属性,甚至可以调用 SQL 统计(aggregate)函数。属性或统计结果被认定为"标量(Scalar)"的结果(而不是持久(persistent state)的实体)。
Iterator results = sess.createQuery(
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group by cat.color")
.list()
.iterator();
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}
10.4.1.4. 绑定参数
接口
Query
提供了对命名参数(named parameters)、JDBC 风格的问号(?)
参数进行绑定的方法。不同于 JDBC,Hibernate 对参数从 0 开始计数。 命名参数(named parameters)在查询字符串中是形如 :name
的标识符。命名参数(named parameters)的优点是:
命名参数(named parameters)与其在查询串中出现的顺序无关
它们可在同一查询串中多次出现
它们本身是自我说明的
//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();
10.4.1.5. 分页
如果你需要指定结果集的范围(希望返回的最大行数/或开始的行数),应该使用
Query
接口提供的方法:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
Hibernate 知道如何将这个有限定条件的查询转换成你的数据库的原生 SQL(native SQL)。
10.4.1.6. 可滚动遍历(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) );
}
cats.close()
请注意,使用此功能需要保持数据库连接(以及游标(cursor))处于一直打开状态。如果你需要断开连接使用分页功能,请使用
setMaxResult()
/setFirstResult()
。
10.4.1.7. 外置命名查询(Externalizing named queries)
你可以在映射文件中定义命名查询(named queries)。如果你的查询串中包含可能被解释为 XML 标记(markup)的字符,别忘了用
CDATA
包裹起来。
<query name="ByNameAndMaximumWeight"
><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight
> ?
] ]></query
>
参数绑定及执行以编程方式(programatically)完成:
Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
请注意实际的程序代码与所用的查询语言无关,你也可在元数据中定义原生 SQL(native SQL)查询,或将原有的其他的查询语句放在配置文件中,这样就可以让 Hibernate 统一管理,达到迁移的目的。
也请注意在
<hibernate-mapping>
元素中声明的查询必须有一个全局唯一的名字,而在 <class>
元素中声明的查询自动具有全局名,是通过类的全名加以限定的。比如 eg.Cat.ByNameAndMaximumWeight
。
10.4.2. 过滤集合
集合过滤器(filter)是一种用于一个持久化集合或者数组的特殊的查询。查询字符串中可以使用
"this"
来引用集合中的当前元素。
Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);
返回的集合可以被认为是一个包(bag,无顺序可重复的集合(collection)),它是所给集合的副本。 原来的集合不会被改动(这与“过滤器(filter)”的隐含的含义不符,不过与我们期待的行为一致)。
请注意过滤器(filter)并不需要
from
子句(当然需要的话它们也可以加上)。过滤器(filter)不限定于只能返回集合元素本身。
Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();
即使无条件的过滤器(filter)也是有意义的。例如,用于加载一个大集合的子集:
Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();
10.4.3. 条件查询(Criteria queries)
HQL 极为强大,但是有些人希望能够动态的使用一种面向对象 API 创建查询,而非在他们的 Java 代码中嵌入字符串。对于那部分人来说,Hibernate 提供了直观的
Criteria
查询 API。
Criteria crit = session.createCriteria(Cat.class);
crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();
The
Criteria
and the associated Example
API are discussed in more detail in 第 16 章 条件查询(Criteria Queries).
10.4.4. 使用原生 SQL 的查询
你可以使用
createSQLQuery()
方法,用 SQL 来描述查询,并由 Hibernate 将结果集转换成对象。请注意,你可以在任何时候调用 session.connection()
来获得并使用 JDBC Connection
对象。 如果你选择使用 Hibernate 的 API,你必须把 SQL 别名用大括号包围起来:
List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("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 {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list()
SQL queries can contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in 第 17 章 Native SQL 查询.