- 译者序
- 前言
- 本书怎么使用
- 本书排版字体约定
- 本书网站
- 致谢
- 第一部分 Hibernate 快速入门
- 第 1 章 安装和设置
- 第 2 章 映射简介
- 第 3 章 驾驭 Hibernate
- 第 4 章 集合与关联
- 第 5 章 更复杂的关联
- 第 6 章 自定义值类型
- 第 7 章 映射标注
- 第 8 章 条件查询
- 第 9 章 浅谈 HQL
- 第二部分 与其他工具的集成
- 第 10 章 将 Hibernate 连接到 MySQL
- 第 11 章 Hibernate 与 Eclipse:Hibernate Tools 使用实战
- 第 12 章 Maven 进阶
- 第 13 章 Spring 入门:Hibernate 与 Spring
- 第 14 章 画龙点睛:用 Stripes 集成 Spring 和 Hibernate
- 附录 A Hibernate 类型
- 附录 B Criteria API
- 附录 C Hibernate SQL 方言
- 附录 D Spring 事务支持
- 附录 E 参考资源
- 作者简介
- 封面介绍
投影和聚合的条件查询
如果你对 SQL 比较熟悉,那么应该明白本节标题的意思。如果不明白的话,也不要担心,它们其实相当简单。投影只是说,你并不是需要一个表中的所有信息,而是只需要其中的一部分。在像 Hibernate 这样的面向对象的环境中,投影就是指不必检索回一个完整的对象,只需要对象的一两个属性。聚合就是标识一些属性,并计算针对这些属性的统计信息,例如计算总和、最大值、最小值以及平均值。
在 Hibernate 3 以前,不使用 HQL 就没办法进行这样的操作,所以这是 Hibernate 3 对 Criteria API 的一个很好的扩展。我们先来看看一些示例。先来个简单的例子,假设我们想打印所有标题包含字母"v"的曲目的标题,但不必加载任何一个完整的 Track 对象。
应该怎么做
例 8-13 演示了一个方法,它使用 Criteria API 提供的投影功能来实现这一目的。
例 8-13:对单一属性的简单投影
/**
*Retrieve the titles of any tracks that contain a particular text string.
*
*@param text the text to be matched, ignoring case, anywhere in the title.
*@param session the Hibernate session that can retrieve data.
*@return the matching titles, as strings.
*/
public static List titlesContainingText(String text, Session session){
Criteria criteria=session.createCriteria(Track.class);
criteria.add(Restrictions.like("title",text, MatchMode.ANYWHERE).❶
ignoreCase());
criteria.setProjection(Projections.property("title"));❷
return criteria.list();
}
❶这行演示了使用 MatchMode 接口来避免执行字符串处理,也不用为了指定想要的字符串匹配模式而记住特定的“%”字符的用法。
❷这里使用 Projections 类来告诉 criteria,我们想要进行一个投影操作,我们对取回找到的曲目的 title 属性特别感兴趣。
像我们的其他条件查询一样,这个方法返回的也是一个 List 对象。但是确切地说,到底是什么内容的列表呢?Criteria 是在 Track 类的基础上创建的,但是由于我们只需要检索回曲目的标题属性,所以构造并返回整个 Track 对象并没有多大意义。事实上,在这种情况下,只要将整个对象投影到一个属性,就可以得到与该属性相关联的类型的一个列表。在这个例子中,因为它是一个字符串属性,所以得到的就是一个包含 String 实例的 List 对象。
注意:这样做确实有意义!
为了检验效果,可以将这个方法添加到 QueryTest.java 中,修改 main()函数以调用它,如下所示:
System.out.println(titlesContainingText("v",session));
运行这个版本的程序,将产生以下输出:
qtest:
[java][Video Killed the Radio Star, Gravity's Angel]
很显然,通过投影也可以检索回那些不在 Restrictions 表达式中的各属性。如果我们想得到曲目长度,可以这样做:
criteria.setProjection(Projections.property("playTime"));
它的输出结果是:
qtest:
[java][00:03:49,00:06:06]
当然,方法名称看起来好像不对,输出结果也不明了。如果我们既想取回标题,也想取回长度,那该怎么办?其实也很简单,如例 8-14 所示。
例 8-14:投影到两个属性
/**
*Retrieve the titles and play times of any tracks that contain a
*particular text string.
*
*@param text the text to be matched, ignoring case, anywhere in the title.
*@param session the Hibernate session that can retrieve data.
*@return the matching titles and times wrapped in object arrays.
*/
public static List titlesContainingTextWithPlayTimes(String text,
Session session){
Criteria criteria=session.createCriteria(Track.class);
criteria.add(Restrictions.like("title",text, MatchMode.ANYWHERE)
.ignoreCase());
criteria.setProjection(Projections.projectionList().❶
add(Projections.property("title")).❷
add(Projections.property("playTime")));
return criteria.list();
}
❶projectionList()方法创建一个 ProjectionList 实例,它可以包含应用于一个条件查询的多个投影选择。注意,我们正在使用的是例 8-5 介绍的简洁的链式标记法,这样就不需要再声明一个变量来持有这个实例了。
❷接着,我们只要将所有需要的投影添加到 ProjectionList,再将这个 ProjectionList 实例传递给条件查询对象的 setProjection()方法。
这里最不容易处理的是从查询返回的结果。和前面的一样,它也是一个 List,但现在每个列表元素要包含多个值,而且这些值的类型还可能不同。Hibernate 采用的办法是返回一个对象数组的列表。以下这段代码用于显示返回的列表,看起来有些复杂:
for(Object o:titlesContainingTextWithPlayTimes("v",session)){
Object[]array=(Object[])o;
System.out.println("Title:"+array[0]+
"(Play Time:"+array[1]+')');
}
它将输出以下内容:
qtest:
[java]Title:Video Killed the Radio Star(Play Time:00:03:49)
[java]Title:Gravity's Angel(Play Time:00:06:06)
确实,这段代码看起来很丑,你绝对不应该在现实中按这样的方式来使用投影。对象/关系映射系统的要点就是你可以只返回对象,在需要一些属性时,再用这些对象来得到你要的属性。这样的话,为什么投影要支持多个值?嗯,事实上是存在好的理由的,它与我们在本节开始介绍的“聚合”的概念有关。很多时候在使用投影时,经常会得到原来根本不直接属于任何对象的值,只是这些值会基于某些对象的属性。例 8-15 演示了一个方法,它会输出数据库中的每种曲目来源媒介,以及来自这种媒介的所有曲目的数量,还有这种媒介的曲目的最长播放时间。
例 8-15:带有聚合的投影
/**
*Print statistics about various media types.
*
*@param session the Hibernate session that can retrieve data.
*/
public static void printMediaStatistics(Session session){
Criteria criteria=session.createCriteria(Track.class);
criteria.setProjection(Projections.projectionList().❶
add(Projections.groupProperty("sourceMedia")).❷
add(Projections.rowCount()).❸
add(Projections.max("playTime")));❹
for(Object o:criteria.list()){❺
Object[]array=(Object[])o;
System.out.println(array[0]+"track count:"+array[1]+
";max play time:"+array[2]);
}
}
注意:现在我们正在利用数据库的功能做些很有意思的事!
这个例子涉及我们目前为止见到的许多事情:
❶和以前一样,我们正在创建一个 ProjectionList 实例来持有想要查询返回的各个项。
❷groupProperty()方法与我们目前使用过的 property()方法类似,但它是告诉 Hibernate 将指定的属性对记录进行分组,所有值相同的记录就成为结果集中的一条记录。分组是执行聚合操作的关键,能让我们为投影增加聚合值。
❸rowCount()投影不需要任何参数,因为它只是返回分组到当前结果集中的记录的总数(基于我们的 groupProperty()的值,sourceMedia)。这就是我们计算属于每种来源媒介类型的曲目数量的方法。
❹max()投影返回分组的结果集中某个属性的最大值。
❺最后,我们用类似前面例子中创建的输出循环,循环遍历条件查询返回的 Object 数组的 List,并输出它们。
在 QueryTest.java 的 main()函数中调用这个方法很简单:
printMediaStatistics(session);
它会生成以下输出:
qtest:
[java]CD track count:4;max play time:00:07:39
[java]VHS track count:1;max play time:00:03:49
[java]STREAM track count:1;max play time:00:07:05
[java]null track count:1;max play time:00:00:10
这一结果应该会让你对投影和聚合的真正价值有所了解(null 媒介类型供我们测试使用)。
不过,能够看到,这一输出并没有按任何顺序进行排序。我们可能想进行排序,但是你怎么对投影结果排序呢?答案就是我们需要看看 Criteria API 中的另一个改进。你可以为对象和属性分配“别名”,以供我们处理使用(就像在数据库查询语言中为表和列起的别名一样),而不论它们是直接来自数据库,还是来自投影和聚合操作。这样,我们按照来源媒介进行排序就容易了,只需要为分组后的属性添加一个别名:
add(Projections.groupProperty("sourceMedia").as("media")).
这条语句声明了一个"media"别名,接着就可以通过这一别名进行排序,就像我们前面对普通的对象属性进行排序一样:
criteria.addOrder(Order.asc("media"));
经过这些变化后,我们就可以看到排序后的输出内容:
qtest:
[java]null track count:1;max play time:00:00:10
[java]CD track count:4;max play time:00:07:39
[java]STREAM track count:1;max play time:00:07:05
[java]VHS track count:1;max play time:00:03:49
之所以使用别名还有些其他原因,使用投影也还可以完成更多的事情,但是接下来要介绍条件查询的其他方面。在本章最后,我们将介绍到哪里可以学习到更多的相关内容。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论