您的当前位置:首页正文

第20章 Hibernate的查询与更新技术

2021-01-17 来源:步旅网


第20章 Hibernate的查询与更新技术

Hibernate框架虽然可以使用Session接口的get或load方法装载持久化对象,但这只是根据主键来检索数据,局限性非常大。为此,Hibernate框架提供了很多强大的查询技术,这些查询技术包括标准(Criteria)查询API和HQL,除此之外,Hibernate还支持直接使用SQL,这将使Hibernate在操作数据库方面更加灵活。HQL和SQL不仅可以查询记录,而且还可以对记录进行增、删、改操作。本章的主要内容如下:

󰂉 标准查询API概述;

󰂉 标准查询API的约束条件;

󰂉 对标准查询API的查询结果进行分页; 󰂉 对查询结果进行排序; 󰂉 多个Criteria之间的关联; 󰂉 聚合与分组;

󰂉 用标准查询API聚合字段值; 󰂉 使用QBE(Query By Example);

󰂉 HQL的From、Select、Where、Update、Delete、Insert语句; 󰂉 在HQL中使用命名参数; 󰂉 使用HQL进行分页;

󰂉 使用HQL进行排序和分组; 󰂉 使用SQL。

20.1 标准(Criteria)查询API

在Hibernate框架中提供了3种获得数据的方法:标准查询API、HQL和SQL。其中标准查询API可以建立基于Java的嵌套的、结构化的查询表达式,并提供了编译时语法检查的功能,而这在使用HQL和SQL时是无法办到的。在Hibernate 3中,标准查询API还提供了投影(projection)、聚合(aggregation)和分组(group)的方法。

20.1.1 实例:一个简单的例子

要想使用标准查询API,就要用到org.hibernate.Criteria接口。通过Session的createCriteria方法可以创建Criteria对象。如果想查询表中的数据,可以使用Criteria接口的list方法。这个方法将以java.util.List对象的形式返回所有被装载的持久化对象(查询到的记录)。下面的例子使用list方法来查询MyMessage对象。在这个例子中使用Criteria

第20章 Hibernate的查询与更新技术

接口的setMaxResults方法来限制返回的最大持久化对象数。

在执行MyCriteria程序后,在控制台将输出如下的信息:

package chapter20; import hibernate.*;

import org.hibernate.*; import chapter17.entity.*; import java.util.*;

public class MyCriteria {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 使用createCriteria方法创建一个Criteria对象

Criteria crit = session.createCriteria(MyMessage.class); // 限制只返回前两个持久化对象

crit.setMaxResults(2); // 开始查询持久化对象

List messages = crit.list(); // 输出查询到的所有持久化对象中的信息 for(MyMessage message: messages) {

System.out.print(message.getId() + \" \" + message.getName()); System.out.println(); }

session.close(); } }

从上面的输出结果可以看出,标准查询API最终也被翻译成SQL语句,并将生成的SQL语句交由相应的DBMS来执行,最后返回查询结果。在最后两行是查询到的两个MyMessage对象中的属性值。即使t_messages表中的记录多于两条,MyCriteria程序仍然会只输出查询结果集中前两个持久化对象的属性值。这是由于调用了Criteria接口的setMaxResult方法限制了返回的持久化对象数。该方法也可以用来对查询结果进行分页(将在后面的部分详细介绍)。

Hibernate: select this_.id as id0_0_, this_.name as name0_0_ from t_message this_ limit ? 1 bike 2 car

20.1.2 查询的约束条件

在标准查询API中可以非常容易地使用Criteria接口的add方法为查询增加约束条件。每一个查询条件是一个SimpleExpression对象,由org.hibernate.criterion.Restrictions类的静态方法获得相应的SimpleExpression对象。如查询id属性等于2的MyMessage对象的代码如下:

Criteria crit = session.createCriteria(MyMessage.class); // eq方法表示“等于”

crit.add(Restrictions.eq(\"id\List messages = crit.list();

·613·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

除了eq方法外,还可以使用如下的方法来表示其他的逻辑关系。

标准查询API不仅能搜索精确的结果,还能搜索模糊的结果。在SQL语句中使用like来查询模糊的结果,而在标准查询API中可以使用like或ilike方法来查询模糊的结果。like方法不区分大小写,而ilike方法区分大小写(实际上,所谓不区分大小写,只是通过MySQL的lower函数将字段值都转换成小写,然后再将要比较的值也转换成小写,最后再进行比较,读者可以将这两个方法生成的SQL语句显示出来进行对比)。在进行模糊的查询时可以使用如下两种方法。

1.使用通配符

public static SimpleExpression ne(String propertyName, Object value);// != public static SimpleExpression lt(String propertyName, Object value);// < public static SimpleExpression gt(String propertyName, Object value);// > public static SimpleExpression le(String propertyName, Object value);// <= public static SimpleExpression ge(String propertyName, Object value);// >=

这种方式和SQL语句的like子句的语法类似。使用%作为通配符,如下面的代码查询所有name属性包含msg的MyMessage对象。

// 创建一个Criteria对象 Criteria crit = session.createCriteria(MyMessage.class); crit.add(Restrictions.like(\"name\ 添加like模糊查询条件 List messages = crit.list(); // 通过list方法获得查询结果

2.使用MatchMode

这种方式通过MatchMode类的静态常量指定了如下所示的4个不同的匹配策略。 󰂉 MatchMode.ANYWHERE:相当于%msg%。 󰂉 MatchMode.START:相当于msg%。 󰂉 MatchMode.END:相当于%msg。

󰂉 MatchMode.EXACT:精确匹配,相当于msg。

如下面的代码也可以查询所有name属性中包含msg的MyMessage对象。

// 创建一个Criteria对象 Criteria crit = session.createCriteria(MyMessage.class); // 通过匹配模式添加like模糊查询条件 crit.add(Restrictions.like(\"name\List messages = crit.list();// 通过list方法获得查询结果

如果对于一个Criteria对象,多次使用add方法加入约束条件,那么这些约束条件之间的关系的默认值是and。也可以使用LogicalExpression将它们的关系改为or,如下面的代码所示:

// 创建一个Criteria对象 Criteria crit = session.createCriteria(MyMessage.class);

SimpleExpression id = Restrictions.eq(\"id\设置id等于2

SimpleExpression name = Restrictions.like(\"name\ANYWHERE); // 将id和name的关系改为or LogicalExpression orExp = Restrictions.or(id, name); ·614 ·

第20章 Hibernate的查询与更新技术

crit.add(orExp); // 添加“或”关系 List results = crit.list(); // 通过list方法获得查询结果

Hibernate将根据上面的代码生成如下的SQL语句。

虽然LogicalExpression类可以很好地生成or逻辑关系,但当关系比较复杂的话,使用LogicalExpression类就显得有些麻烦。Hibernate提供了一个更容易的解决方案来生成or逻辑关系,这就是Disjunction类(如果要生成and关系,可以使用Conjunction类)。下面的代码演示了如何使用Conjunction和Disjunction来生成and和or关系。

select this_.id as id0_0_, this_.name as name0_0_ from t_message this_ where (this_.id=? or this_.name like ?)

Criteria crit = session.createCriteria(MyMessage.class); // 创建一个Criteria对象 Criterion id1 = Restrictions.gt(\"id\ // 大于2 Criterion id2 = Restrictions.lt(\"id\ // 小于20

Criterion name = Restrictions.like(\"name\Conjunction conjunction = Restrictions.conjunction(); conjunction.add(id1);

conjunction.add(id2);

Disjunction disjunction = Restrictions.disjunction(); disjunction.add(conjunction); // 添加id1和id2的关系 // name和id1、id2是or关系 disjunction.add(name); crit.add(disjunction); // 向Criteria对象中添加关系 List results = crit.list();

上面的代码生成的SQL语句如下:

除此之外,还可以使用Restrictions类的sqlRestriction方法直接写SQL语句,代码 如下:

crit.add(Restrictions.sqlRestriction(\"{alias}.name like '%d%'\"));

select this_.id as id0_0_, this_.name as name0_0_ from t_message this_ where ((this_.id>? and this_.id也可以在SQL语句中加入“?”作为参数,代码如下:

其中第1个参数中的{alias}是表名,Hibenate在生成SQL语句时,会自动将其替换成t_messages。第2个参数要和第1个参数中的“?”相对应。第3个参数表示在条件中使用的字段类型(也就是name字段的类型,在这里是String类型)。如果查询条件中有多个“?”,第2个和第3个参数要使用数组形式来传递多个参数值和类型。

crit.add(Restrictions.sqlRestriction(\"{alias}.name like ?\\"%d%\org. hibernate.type.StringType()));

20.1.3 对查询结果进行分页

分页是Web应用程序中经常要用到的技术。如在一个论坛程序中,可能会有成千上万的帖子,这些帖子肯定不能在一个页面中显示出来,因此就要将这些记录显示在若干页面

·615·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

上。达到这种效果的一般实现方法是单击某一页的链接后,在服务端再重新查询整个记录集,然后通过某些特殊的手段将要在当前页面显示的记录取出,并显示在客户端浏览器中。

实际上,只获得查询结果的部分内容对于不同的数据库来说难易程度也不同。如SQLServer 2000中的实现就比较复杂(要从一个大的结果集中取得满足要求的结果集,效率不是很高),在SQLServer 2005中由于提供新的功能,所以实现分页要比SQL Server 2000容易一些。而在MySQL中,笔者认为它的分页功能是最容易实现的,只需要简单地使用limit关键字指定开始记录的位置(从0开始)和要获取的记录数即可,如下面的代码只取查询结果中的从第2条记录开始的两条记录。

从上面的描述可以看出,对于不同的数据库来说,各种数据库的分页方法各不相同,有的效率高,有的效率并不高。为了能统一大多数常用数据库的分页方法,并尽可能地提高效率,Hibernate提供了一种非常简便的方式来实现这个目标。在Criteria接口中有两个方法:setFirstResult方法和setMaxResults方法。其中setFirstResult设置了记录的开始位置(0表示第1条记录),setMaxResults设置了返回的记录数。如下面的代码从t_message表中获得了从第2条记录开始的两条记录。

select * from t_message limit 1, 2

如果不使用setFirstResult方法设置记录的开始位置,默认值是0。Hibernate会根据不同的数据库采用不同的分页策略,由于本书使用的数据库是MySQL,因此,Hibernate会使用limit进行分页处理。其实,setFirstResult和setMaxResults方法设置的值就是limit关键字的第1个和第2个参数值。Hibernate根据上面的代码生成的SQL语句如下:

下面是查询t_message表中的第2条和第3条记录的完整实现代码。

Criteria crit = session.createCriteria(MyMessage.class);

crit.setFirstResult(1); // 返回结果的开始位置(从第2条记录开始),第1行记录 的位置是0

crit.setMaxResults(2); // 返回的记录(持久化对象)数 List messages = crit.list();

select this_.id as id0_0_, this_.name as name0_0_ from t_message this_ limit ?, ?

package chapter20; import hibernate.*;

import org.hibernate.*; import chapter17.entity.*; import java.util.*;

public class Pagination {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 根据MyMessage创建Criteria对象实例

Criteria crit = session.createCriteria(MyMessage.class); // 设置取记录位置

crit.setFirstResult(1);

// 设置要返回的MyMessage对象数 crit.setMaxResults(2);

// 返回指定数量的MyMessage对象

List messages = crit.list(); ·616 ·

第20章 Hibernate的查询与更新技术

// 输出返回的MyMessage对象的id和name属性值 for (MyMessage message : messages) {

System.out.print(message.getId() + \" \" + message.getName()); System.out.println(); }

session.close(); } }

20.1.4 实例:实现Web分页功能

在本节给出一个实例来演示如何整合Struts 2和Hibernate(使用标准查询API)实现Web分页。在该实例中服务端使用了Action类,客户端在JSP页面中使用Struts 2标签来读取分页数据,并生成完整的

标签。

PaginationAction是处理分页请求的Action类。在该类中通过读取Action参数来确定查询哪个实体Bean,以及显示实体Bean的属性和每页显示的行数。PaginationAction类的实现代码如下:

package chapter20.action;

import hibernate.HibernateSessionFactory; import org.hibernate.Criteria; import org.hibernate.Session;

import com.opensymphony.xwork2.*; import java.util.*;

public class PaginationAction extends ActionSupport {

private String entity; // 封装entity参数的属性 private String fields; // 封装fields参数的属性 private int rows; // 封装rows参数的属性 private int currentPage; // 封装currentPage请求参数的属性 private List records = new ArrayList(); // 返回成功装载的持久化对象 // 处理控制逻辑的execute方法

public String execute() throws Exception {

Session session = HibernateSessionFactory.getSession(); // 获得Hibernate Session对象 // 根据entity属性值创建Criteria对象实例

Criteria crit = session.createCriteria(entity); crit.setFirstResult((currentPage - 1) * rows); // 设置返回记录的开始行 crit.setMaxResults(rows); // 设置返回的记录行数 List list = crit.list(); // 使用list方法查询结果 // 将装载的持久化对象返回给客户端的JSP页面 for (Object obj : list) {

records.add(obj); }

session.close(); // 关闭Hibernate Session对象 return SUCCESS; }

// fields属性的getter方法 public String getFields()

·617·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

{

return fields; }

// fields属性的setter方法

public void setFields(String fields) {

this.fields = fields; }

// rows属性的getter方法 public int getRows() {

return rows; }

// rows属性的setter方法

public void setRows(int rows) {

this.rows = rows; }

// entity属性的getter方法 public String getEntity() {

return entity; }

// entity属性的setter方法

public void setEntity(String entity) {

this.entity = entity; }

// currentPage属性的getter方法 public int getCurrentPage() {

return currentPage; }

// currentPage属性的setter方法

public void setCurrentPage(int currentPage) {

this.currentPage = currentPage; }

// records属性的getter方法

public List> getRecords() {

return records; }

// records属性的setter方法

public void setRecords(List> records) {

this.records = records; }

// fieldList属性的getter方法 public String[] getFieldList() {

return fields.split(\ } }

PaginationAction类的配置代码如下:

·618 ·

第20章 Hibernate的查询与更新技术

/chapter20/pagination.jsp

chapter17.entity.MyMessage id,name 4

pagination.jsp页面负责显示服务端返回的信息。该页面的代码如下:

在浏览器地址栏中输入如下的URL:

浏览器显示的效果如图20.1所示。

<%@ page pageEncoding=\"UTF-8\"%>

<%@ taglib prefix=\"s\" uri=\"/struts-tags\"%>

分页

\">

   

http://localhost:8080/webdemo/chapter20/pagination.action?currentPage=1

·619·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

图20.1 Web分页

单击图20.1所示的页码,就会显示相应页的信息。读者也可以通过配置标签参数对其他的数据表进行分页。

20.1.5 只获得一个持久化对象

在某些情况下,只需要获得一条记录或一个持久化对象。如在统计记录数时,只想返回一条只包含一个字段的记录,或是只想返回查询结果集的第1条记录。在这种情况下,可以使用Criteria接口的uniqueResult方法。这个方法返回了一个Object对象,而不是一个List对象。如果未查到记录,uniqueResult方法返回null。如果无法保证返回的结果集只有一条记录,可以使用setMaxResults方法将返回的结果集的记录数设为1,如下面的代码所示:

public class UniqueResult { public static void main(String[] args) { Session session = HibernateSessionFactory.getSession(); // 获得一个Hibernate Session 对象 Criteria crit = session.createCriteria(MyMessage.class); crit.setMaxResults(1); // 设置只返回一条记录 crit.add(Restrictions.eq(\"id\// 添加一个id属性等于1的条件 MyMessage message = (MyMessage)crit.uniqueResult(); // 装载第一个持久化对象 // 如果成功装载MyMessage对象,输出该对象的name属性值 if(message != null) System.out.println(message.getName()); session.close(); // 关闭Hibernate Session对象 } }

󰀈注意:在调用uniqueResult方法时必须保证查询结果集只有一条记录,否则uniqueResult

方法将抛出一个org.hibernate.NonUniqueResultException异常。

·620 ·

第20章 Hibernate的查询与更新技术

20.1.6 对查询结果进行排序

在标准查询API中可以使用org.hibernate.criterion.Order类对查询结果进行排序。Order类有两个静态方法:asc和desc。这两个方法都返回Order对象。使用这两个方法可以指定要排序的持久化对象的属性。下面的代码演示了如何使用Order对结果集进行排序。

在执行上面的代码后,Hibernate将生成如下的SQL语句。

public class SortResult {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession();

Criteria crit = session.createCriteria(MyMessage.class); crit.addOrder(org.hibernate.criterion.Order.desc(\"id\")); // 对查询结果进行排序

List messages = crit.list(); // 返回相应的MyMessage对象 // 输出所有被装载的MyMessage对象的id和name属性值 for (MyMessage message : messages) {

// 输出当前Message对象的id和name属性值

System.out.print(message.getId() + \" \" + message.getName()); System.out.println(); }

session.close(); // 关闭Hibernate Session对象 } }

select this_.id as id0_0_, this_.name as name0_0_ from t_message this_ order by this_.id desc

20.1.7 多个Criteria之间的关联

在标准查询API中也可以处理多个实体Bean关联的情况。如像Order和Customer对象这样的多对一关系,要想描述这种关系,需要使用Criteria接口的createCriteria方法创建另一个Criteria对象,这个createCriteria方法的参数是一个属性名。要建立Order和Customer对象的多对一关系,要先使用Session接口的createCriteria方法为Order建立一个Criteria对象,然后再使用这个Criteria对象的createCriteria方法为Order类的customer属性创建一个Criteria对象,代码如下:

public class Associations {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 建立Order和Customer对象的多对一关系

Criteria crit1 = session.createCriteria(Order.class); Criteria crit2 = crit1.createCriteria(\"customer\"); // 添加约束条件

crit1.add(Restrictions.ne(\"number\ crit2.add(Restrictions.eq(\"name\

·621·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

crit2.addOrder(org.hibernate.criterion.Order.desc(\"name\")); // 按name字段降序排序 List orders = crit1.list(); for (Order order : orders) {

// 输出当前Order对象的number和name属性值 System.out.print(order.getNumber() + \" \" + order.getCustomer(). getName());

System.out.println(); }

session.close(); } }

在上面的代码中除了建立了Order和Customer的多对一的关系外,还设置了查询条件(使用Criteria接口的add方法为crit1和crit2添加约束,详见20.1.2节的内容)。

20.1.8 聚合和分组

在标准查询API中可以使用org.hibernate.criterion.AggregateProjection类进行聚合操作,并且使用org.hibernate.criterion.Projections类的相应的静态方法来建立AggregateProjection对象,最后使用Criteria接口的setProjection方法设置相应的AggregateProjection对象。如获得表的记录数的代码如下:

Criteria crit = session.createCriteria(MyMessage.class); // 通过Projections类的rowCount方法获得记录数 crit.setProjection(Projections.rowCount()); Integer value = (Integer)crit.uniqueResult(); System.out.println(value);

要注意的是,setProjection方法只在最后一次有效。也就是说,对一个Criteria对象多次使用setProjection方法后,后一次将覆盖前一次的聚合操作。如果要为Criteria对象设置多个聚合操作,可以使用org.hibernate.criterion.ProjectionList类。下面的代码是使用聚合方法的一个完整示例。

public class MyProjections {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 创建Hibernate Session对象 Criteria crit = session.createCriteria(MyMessage.class); // 创建Criteria对象 crit.setProjection(Projections.rowCount()); // 统计记录数 Integer value = (Integer)crit.uniqueResult(); // 使用uniqueResult方法统计记录的行数 System.out.println(value);

crit.setProjection(Projections.max(\"id\")); // 求id属性的最大值 // 使用uniqueResult方法对id字段的最大值 value = (Integer)crit.uniqueResult(); System.out.println(value);

crit.setProjection(Projections.sum(\"id\")); // 求id属性之和 value = (Integer)crit.uniqueResult(); // 使用uniqueResult方 法对id字段值之和

·622 ·

第20章 Hibernate的查询与更新技术

System.out.println(value);

ProjectionList projList = Projections.projectionList(); // 创建ProjectionList对象 // 同时使用三个聚合方法

projList.add(Projections.rowCount()); projList.add(Projections.max(\"id\")); projList.add(Projections.sum(\"id\")); crit.setProjection(projList); List results = crit.list(); // 同时执行3个聚合操作 Object[] array = (Object[]) results.get(0); for(int i = 0; i < array.length; i++) {

System.out.println(array[i]); }

session.close(); // 关闭Hibernate Session对象 } }

如果Criteria对象有多个聚合操作,Hibernate仍返回一条记录,但这条记录并不是持久化对象,而是一个包含了每一个聚合值的Object数组(一个数组元素表示一个聚合值)。如执行上面的代码后将生成如下的4条SQL语句。

由于上面的SQL语句只有3个字段,因此,Hibernate生成了一个长度为3的Object数组。聚合与分组也可混合使用。下面代码是一个同时使用聚合与分组方法的完整的示例。

select count(*) as y0_ from t_message this_

select max(this_.id) as y0_ from t_message this_ select sum(this_.id) as y0_ from t_message this_

select count(*) as y0_, max(this_.id) as y1_, sum(this_.id) as y2_ from t_message this_

在执行上面的代码后,Hibernate将生成如下的SQL语句。

public class ProjectionsGroup {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); Criteria crit = session.createCriteria(MyMessage.class); ProjectionList projList = Projections.projectionList(); projList.add(Projections.rowCount()); // 使用聚合方法 projList.add(Projections.groupProperty(\"name\")); // 使用分组方法 crit.setProjection(projList); List list = crit.list(); // 执行聚合、分组操作 Object[] array = null; // 输出查询结果

for (int i = 0; i < list.size(); i++) {

array = (Object[]) list.get(i); // 将当前元素转换成对象数组 for(int j = 0; j < array.length; j++) System.out.print(array[j] + \" \"); System.out.println(); }

session.close(); // 关闭Hibernate Session对象 } }

select count(*) as y0_, this_.name as y1_ from t_message this_ group by

·623·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

this_.name

20.1.9 使用QBE(Query By Example)

除了使用标准查询API后,还可以用QBE来实现查询功能。QBE可以直接根据对象实例来指定查询条件。从前面的内容可以知道,在标准查询API中要使用Criterion对象来指定查询条件。而org.hibernate.criterion.Example类实现了Criterion接口,并且Example类可以使用create方法根据一个实体Bean对象来建立Criterion对象(实际上,QBE的意思就是使用Example类来生成Criterion对象,并进行查询)。下面的代码是一个使用Example查询数据的简单的例子。

在执行上面的代码后,Hibernate将生成如下的SQL语句。

Criteria crit = session.createCriteria(MyMessage.class); MyMessage message = new MyMessage(); message.setName(\"我的信息\");

crit.add(Example.create(message)); // 用Example类的create方法生成一个 Criterion对象 List result = crit.list();

从上面SQL语句可以看出,Hibernate根据实体Bean对象生成了用于查询的SQL语句。但大家要注意一下,在默认情况下,Example会将实体Bean中的所有属性(不包括主键属性)作为查询条件放到where子句中,但会忽略值为null的属性,也就是说,在生成SQL语句时,值为null的属性不会出现在where子句中。

但有的属性类型是Java简单类型(如int),而不是复杂类型(如Integer)。对于Java简单类型,如int,在未赋值时的默认值是0。而Example并不会过滤值为0的属性。因此,这就需要使用Example类的excludeZeroes或excludeNone方法来忽略值为0的属性,也可以使用excludeProperty方法指定不参与查询的属性,代码如下:

select this_.id as id0_0_, this_.name as name0_0_ from t_message this_ where (this_.name=?)

在执行上面的代码后,Hibernate将生成如下的SQL语句。

·624 ·

Criteria crit = session.createCriteria(Person.class);

Person person = new Person(); // 创建Person对象实例 java.util.Calendar cal = java.util.Calendar.getInstance(); cal.set(1955, 4, 7); // 设置指定日期

person.setBirthday(cal.getTime()); // 初始化birthday属性 Name name = new Name(); // 创建Name对象实例 name.setFirst(\"Gannon\"); // 初始化first属性 name.setLast(\"gates\"); // 初始化last属性 person.setName(name);

Example example = Example.create(person); example.excludeZeroes(); // person对象中值为0的属性不值出现where 子句中 example.excludeProperty(\"birthday\"); // birthday不会出现在where子句中 crit.add(example);

List result = crit.list();

select this_.id as id1_0_, this_.birthday as birthday1_0_, this_.first as

第20章 Hibernate的查询与更新技术

first1_0_, this_.last as

last1_0_ from t_persons this_ where (this_.first=? and this_.last=?)

使用Example类的enableLike方法可以将所有String类型的属性都使用like,而不是用“=”进行查询,如下面的代码所示:

Criteria crit = session.createCriteria(Person.class); Person person = new Person();

java.util.Calendar cal = java.util.Calendar.getInstance(); cal.set(1955, 4, 7);

person.setBirthday(cal.getTime()); Name name = new Name(); name.setFirst(\"Gannon\");

name.setLast(\"gates\"); person.setName(name);

Example example = Example.create(person);

example.enableLike(); // 所有String类型的属性都使用like关键字进行查询 crit.add(example);

List result = crit.list();

在执行上面的代码后,Hibernate将生成如下的SQL语句。

在使用Example类生成查询条件时要注意,实体Bean中参与查询的属性之间的关系都是and。

select this_.id as id1_0_, this_.birthday as birthday1_0_, this_.first as first1_0_, this_.last as last1_0_ from t_persons this_ where (this_.birthday=? and this_.first like ? and this_.last like ?)

20.2 HQL和SQL技术

HQL(Hibernate Query Language)是Hibernate框架提供的另一种操作数据的方式。其在语法上非常接近SQL,但它们不同的是HQL是面向对象的,也就是说,HQL所操作的都是持久化对象,而不是表。使用HQL操作数据可以直接返回相应的持久化对象。另外,Hibernate也可以将SQL查询出来的数据转换为持久化对象。

20.2.1 实例:使用HQL的第一个例子

最简单的HQL查询是根据指定的实体Bean返回所有满足条件的持久化对象。在这个查询中只含有一个from关键字,from后面直接跟着实体Bean的类名,如下面的代码所示:

from MyMessage

󰀈注意:HQL和SQL不同,在HQL语句中实体Bean的名称和属性名是区分大小写的,

而SQL语句中的表名和字段名不区分大小写,但HQL语句中的关键字不区分大小写,如from也可以写成From。

通过编程方式执行HQL语句首先要使用Session接口的createQuery方法建立一个

·625·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

Query对象。然后使用Query类的list方法获得满足条件的持久化对象。createQuery方法的定义如下:

下面的代码是一个执行HQL语句的例子,在这个例子中返回了MyMessage类的所有持久化对象。

public Query createQuery(String queryString) throws HibernateException;

与标准查询API一样,Hibernate也会根据HQL语句来生成相应的SQL语句,如执行上面的代码后,Hibernate将生成如下的SQL语句的HQL语句。

package chapter20; import hibernate.*;

import org.hibernate.*; import chapter17.entity.*; import java.util.*; public class FirstHQL {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); String hql = \"from MyMessage\"; // 定义HQL语句 Query query = session.createQuery(hql); // 通过提定的SQL语句创 建Query对象 List messages = query.list(); // 执行HQL,并返回相应的 MyMessage对象 // 输出所有被装载的MyMessage对象的id和name属性信息 for (MyMessage message : messages) {

// 输出当前MyMessage对象的id和name属性值

System.out.print(message.getId() + \" \" + message.getName()); System.out.println(); }

session.close(); } }

select mymessage0_.id as id0_, mymessage0_.name as name0_ from t_message mymessage0_

20.2.2 From子句:简化实体Bean类名

在20.2.1节已经接触过from子句了。from后面可直接跟实体Bean的类名,但有时实体Bean的类名较长,使用不方便,因此,需要为较长的类名起一个别名,代码如下:

其中as关键字是可选的,因此,也可以使用如下的HQL语句为实体Bean起别名。

如果类名和当前类引用名中的类型冲突时,也可以使用package.classname的方式,代码如下:

from chapter17.entity.MyMessage m ·626 ·

from MyMessage m from MyMessage as m

第20章 Hibernate的查询与更新技术

如果from后面有多个类名,中间用逗号(,)分隔,代码如下:

from后面不但可以跟实体Bean,也可以跟实体Bean的父类,如下面的HQL语句 所示:

由于Object类是所有Java类的父类,因此,上面的HQL将返回所有在Hibernate映射文件中定义的实体Bean。Hibernate会根据上面的HQL语句生成如下的SQL语句。

from java.lang.Object

from EntityBean1 as b1, EntityBean2 as b2

如果想让Hibernate生成查询相关实体Bean对象的SQL语句,可以建立一个父类,让这些实体Bean都继承这个父类,如有一个Father类,MyMessage和Employee都继承于该类,代码如下:

public class Father {

... }

public class MyMessage extends Father {

... }

public class Employee extends Father {

... }

select product0_.id as id7_, product0_.name as name7_ from t_products product0_

select productdet0_.id as id8_0_, productdet0_.detail as detail8_0_ from t_product_details productdet0_ where productdet0_.id=? select address0_.id as id6_, address0_.address as address6_ from t_addresses address0_

select productdet0_.id as id8_, productdet0_.detail as detail8_ from t_product_details productdet0_ select person0_.id as id2_, person0_.birthday as birthday2_, person0_.first as first2_, person0_.last as last2_ from t_persons person0_

select order0_.id as id4_, order0_.order_number as order2_4_, order0_.customer_id as customer3_4_ from t_orders order0_

select mymessage0_.id as id0_, mymessage0_.name as name0_ from t_message mymessage0_ select keys0_.key1 as key1_1_, keys0_.key2 as key2_1_, keys0_.data as data1_ from t_keys keys0_

select customer0_.id as id3_, customer0_.name as name3_ from t_customers customer0_

select employee0_.id as id5_, employee0_.name as name5_, employee0_. address_id as address3_5_ from t_employees employee0_

可以使用如下的HQL生成查询MyMessage和Employee对象的SQL语句。

from chapter20.entity.Father

上面的HQL语句生成的SQL语句如下:

select mymessage0_.id as id0_, mymessage0_.name as name0_ from t_message mymessage0_

select employee0_.id as id5_, employee0_.name as name5_, employee0_.

·627·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

address_id as address3_5_ from t_employees employee0_

󰀈注意:如果

from后面跟的是未在Hibernate映射文件中映射的实体Bean,必须使用package.classname的形式,如使用Father类时,必须使用chapter20.entity.Father,而不能只使用Father。

20.2.3 Select子句:选择返回属性

Select子句要比From子句提供了更多的控制。通过Select语句,可以选择返回的属性。如下面的HQL语句只返回MyMessage对象的name属性。

如果使用Select语句,Hibernate就不会返回相应的持久化对象,而是根据情况返回不同类型的List对象。如上面的HQL语句只返回了name属性,那么Hibernate就会返回List类型的List对象。

如果select语句包含了多个属性,Hibernate就会返回List类型的List对象(每一个Object数组元素表示一个属性值)。下面代码是一个演示select子句用法的完整示例。

select name from MyMessage

public class HQLSelect {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession();

String hql = \"select name from MyMessage\"; // select子句只有一个属 性的情况 Query query = session.createQuery(hql); // 根据hql语句创建 Query对象 // 使用list方法装载符合条件的MyMessage对象 List messages = query.list(); for(String name: messages) {

// 输出查询到的实体Bean对象的name属性值 System.out.println(name); }

hql = \"select id,name from MyMessage\"; // select子句包含两个属 性:id和name query = session.createQuery(hql); // 根据hql语句创建Query对象 messages = query.list(); // List对象元素是Object数组 for(Object obj: messages) {

Object[] properties = (Object[])obj;

System.out.println(properties[0] + \" \" + properties[1]); }

session.close(); } }

在运行HQLSelect程序后,Hibernate将输出如下两条SQL语句。

·628 ·

第20章 Hibernate的查询与更新技术

select mymessage0_.name as col_0_0_ from t_message mymessage0_

select mymessage0_.id as col_0_0_, mymessage0_.name as col_1_0_ from t_message mymessage0_

20.2.4 Where子句:指定条件

在HQL语句中可以使用where子句进一步限制要查询的持久化对象。HQL的where子句和SQL的where子句的用法类似,如下面的HQL所示:

如果where子句包含多个条件,中间需要使用or或and指定这些条件之间的关系。下面代码是一个演示where子句用法的完整示例。

select name from MyMessage where id > 2

在运行HQLWhere程序后,Hibernate将输出如下两条SQL语句。

public class HQLWhere {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); String hql = \"select name from MyMessage where id > 1\"; // 包含一个查询条件的where子句 Query query = session.createQuery(hql); // 根据hql语句创建一个Query对象 // 使用list方法装载MyMessage持久化对象 List messages = query.list(); for (Object obj : messages) {

System.out.println(obj); }

// 包含两个查询条件的where子句

hql = \"select id, name from MyMessage where id > 1 and not (name like '%c%')\";

query = session.createQuery(hql); messages = query.list(); for (Object obj : messages) {

Object[] properties = (Object[])obj; System.out.print(properties[0]); System.out.println(properties[1]); }

session.close(); } }

HQL的where子句和SQL的where子句在大多数符号和关键字的使用上类似,但也有一些不同的地方。读者可以访问下面的URL来了解HQL的where子句更详细的信息。

http://www.hibernate.org/hib_docs/v3/reference/en/html/queryhql.html#queryhql-expressions

select mymessage0_.name as col_0_0_ from t_message mymessage0_ where mymessage0_.id>1

select mymessage0_.id as col_0_0_, mymessage0_.name as col_1_0_ from t_message mymessage0_ where mymessage0_.id>1 and (mymessage0_.name not like '%c%')

·629·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

20.2.5 使用命名参数

HQL和JDBC一样,也支持查询参数。但它们不同的是JDBC的查询参数只支持按位置赋值(按“?”出现的位置),而HQL的查询参数同时支持按位置和参数名赋值。HQL查询参数不使用“?”表示,而是使用名称表示(称为命名参数)。HQL命名参数使用冒号(:)开头,如下面的代码所示:

在为命名参数赋值时,需要使用Query接口的setXxx方法,其中Xxx表示为相应数据类型的命名参数赋值,如下面的代码是为整型数据赋值。

String hql = \"from MyMessage where id > :id\"; // 通过参数指定id Query query = session.createQuery(hql); query.setInteger(\"id\1); // 按参数名赋值 from MyMessage where id > :id

HQL的查询参数不仅可以使用Java简单类型作为参数类型,也可以使用实体Bean作为参数类型,代码如下:

// 相当于e. address_id = address.id

String hql = \"from Employee e where e.address = :address\"; Query query = session.createQuery(hql); // 使用setEntity方法为实体Bean类型的参数赋值 query.setEntity(\"address\

下面代码是一个演示命名参数的完整示例。

public class HQLNamedParameters {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 定义带Integer类型命名参数的HQL语句

String hql = \"from MyMessage where id > :id\"; Query query = session.createQuery(hql); // 为Integer类型的命名参数赋值 query.setInteger(\"id\

List messages = query.list(); for (MyMessage message : messages) {

System.out.print(message.getId() + \" \" + message.getName()); System.out.println(); }

Address address = new Address(); address.setId(1);

// 定义带Address类型命名参数的HQL语句

hql = \"from Employee e where e.address = :address\"; query = session.createQuery(hql); // 为Address类型的命名参数赋值

query.setEntity(\"address\ Object obj = query.uniqueResult(); if (obj != null) {

Employee employee = (Employee) obj;

System.out.println(employee.getName());

·630 ·

第20章 Hibernate的查询与更新技术

}

session.close(); } }

在运行HQLNamedParameters程序后,Hibernate将输出如下两条SQL语句。

从上面的第2条SQL语句可以看出,在使用实体Bean作为命名参数类型时,Hibernate会使用该实体Bean的主键(也是数据表的主键字段,address_id)作为where子句的查询条件。

select mymessage0_.id as id0_, mymessage0_.name as name0_ from t_message mymessage0_ where mymessage0_.id>?

select employee0_.id as id5_, employee0_.name as name5_, employee0_.address_id as address3_5_ from t_employees employee0_ where employee0_.address_id=?

20.2.6 使用Query进行分页

Query接口和Criteria接口一样,也可以使用setFirstResult和setMaxResults方法进行分页,如下面代码所示:

String hql = \"from MyMessage \";

Query query = session.createQuery(hql); query.setFirstResult(10); // 从第11条记录开始 query.setMaxResults(10); // 最多返回10条查询结果 List messages = query.list();

Query和Criteria接口的setFirstResult、setMaxResults的使用方法完全一样(关于Criteria接口的这两个方法的详细内容详见20.1.3节中的介绍)。

20.2.7 实例:使用HQL实现Web分页功能

在本节给出一个实例来演示如何整合Struts2和Hibernate(使用HQL)实现Web分页。本实例实现的功能和20.1.4节所使用标准查询API实现的Web分页功能相同,只是服务端的Action类使用了HQL对数据库进行查询。

HQLPaginationAction是处理分页请求的Action类。该类和20.1.4节中的PaginationAction类的功能相同,只是在execute方法中使用了HQL技术来查询数据。HQLPaginationAction类的实现代码如下:

public class HQLPaginationAction extends ActionSupport {

// 封装Action参数的属性 private String entity; private String fields; private int rows;

// 封装currentPage请求参数的属性 private int currentPage; // 返回成功装载的持久化对象

private List records = new ArrayList(); // 使用HQL语句查询数据的execute方法

·631·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

public String execute() throws Exception {

Session session = HibernateSessionFactory.getSession(); // 根据HQL语句获得Query对象

Query query = session.createQuery(\"from \" + entity); // 进行分页处理

query.setFirstResult((currentPage - 1) * rows); query.setMaxResults(rows); List list = query.list(); for (Object obj : list) {

records.add(obj); }

session.close(); return SUCCESS; }

// 由于HQLPaginationAction类和PaginationAction类的getter和setter的实 现代码完全相同,

// 因此,这里省略了这些getter和setter方法 ... }

HQLPaginationAction类的配置代码如下:

其中pagination.jsp页面和20.1.4节中实现的pagination.jsp页面的代码完全相同,请读者参阅20.1.4节中的相关代码。在浏览器地址栏中输入如下的URL:

在浏览器中将会显示和图20.1相同的效果。

/chapter20/pagination.jsp

chapter17.entity.MyMessage id,name 4

http://localhost:8080/webdemo/chapter20/pagination.action?currentPage=1

20.2.8 使用HQL进行排序和分组

在HQL中可以使用order by子句对查询结果进行排序。与SQL中的order by子句一样,使用asc表示升序,使用desc表示降序。如果按多个字段进行排序,中间使用逗号(,)分隔。如下面的HQL语句所示:

也可以使用group by子句对查询结果进行分组,如下面的HQL语句按照MyMessage类的name属性进行分组,并统计每组的记录数。

·632 ·

select name from MyMessage order by id, name desc

第20章 Hibernate的查询与更新技术

order by和group by子句也可以同时使用,如下面的HQL语句所示:

下面代码是一个演示排序和分组的完整示例。

select count(*) from MyMessage group by name

select count(*),name from MyMessage group by name order by name

在运行HQLOrderGroup程序后,Hibernate将输出如下两条SQL语句。

public class HQLOrderGroup {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 使用order by子句对id和name属性进行排序

String hql = \"select name from MyMessage order by id desc, name asc\"; Query query = session.createQuery(hql); // 根据hql语句创建Query对象实例 List messages = query.list(); // 使用list方法装载符合条件的MyMessage对象 // 输出所有被装载的MyMessage对象的name属性值 for(String name: messages) {

System.out.println(name); }

// 使用order by和group by子句分别对id和name属性进行排序和分组

hql = \"select count(*), name from MyMessage group by name order by id\";

query = session.createQuery(hql); List result = query.list(); for(Object obj: result) {

Object[] properties = (Object[])obj; System.out.print(properties[0]); System.out.println(properties[1]); }

session.close(); } }

从上面的SQL语句可以看出,Hibernate将HQL中的order by和group by 子句转换成了SQL的order by 和group by 子句。

select mymessage0_.name as col_0_0_ from t_message mymessage0_ order by mymessage0_.id desc, mymessage0_.name asc

select count(*) as col_0_0_, mymessage0_.name as col_1_0_ from t_message mymessage0_ group by mymessage0_.name order by mymessage0_.id

20.2.9 关联查询

HQL允许进行关联查询,也就是说在查询中使用多个实体Bean。在HQL中支持如下4种连接类型。

󰂉 inner join:内连接;

󰂉 left outer join:左外连接;

·633·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

󰂉 right outer join:右外连接;

󰂉 full join:完全连接(笛卡尔积)。

这4种连接类型和SQL的连接类型非常类似。只是在HQL中要使用实体Bean进行连接,而在SQL中使用的是表连接。如下面的HQL语句从Customer和Order中查询id、name和number属性值。

上面的HQL语句用的是inner join(内连接),由于使用了select 子句,因此,Hibernate会返回一个List对象。上面的HQL语句也可以使用left outer join(左连接)和right outer join(右连接)。但要注意,这两个连接中的outer可以省略,如这两个连接可以写成left join和right join。

如果不使用select子句,Hibernate将以Object数组形式同时返回Customer和Order对象,代码如下:

select c.id, c.name, o.number from Customer c inner join c.orders as o

从上面的代码可以看出,不使用select子句,Hibernate仍然会返回一个List对象,但Object数组中只有两个元素,分别是Customer和Order对象。

如果想优化查询性能,可以使用fetch关键字(放在join后面)。如果使用fetch,Hibenate在查询时只返回Customer对象,如果Customer类的orders是lazy属性,只有访问当前Customer对象的orders属性时,系统才会装载相应的Order对象,代码如下:

String hql = \"from Customer c inner join c.orders as o\"; Query query = session.createQuery(hql); List result = query.list(); for (Object[] obj : result) {

Customer customer = (Customer)obj[0]; Order order = (Order)obj[1];

System.out.print(customer.getId() + \" \" + customer.getName() + \" \" + order.getNumber()); System.out.println(); }

public class HQLAssociations {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); // 使用内连接关联Customer和Order对象 String hql = \"select c.id, c.name, o.number from Customer c inner join c.orders as o\";

Query query = session.createQuery(hql); List result = query.list(); for (Object[] row : result) {

for (int i = 0; i < row.length; i++) {

System.out.print(row[i] + \" \"); }

System.out.println(); }

System.out.println(\"--------------------------------\"); // 不使用select子句,将返回一个List类型的对象 hql = \"from Customer c inner join c.orders as o\";

·634 ·

第20章 Hibernate的查询与更新技术

query = session.createQuery(hql); result = query.list();

for (Object[] obj : result) {

Customer customer = (Customer)obj[0]; Order order = (Order)obj[1];

System.out.print(customer.getId() + \" \" + customer.getName() + \" \" + order.getNumber()); System.out.println(); }

System.out.println(\"--------使用fetch----------\"); // 使用fetch关键字,只返回List对象

hql = \"from Customer c inner join fetch c.orders as o\"; query = session.createQuery(hql);

List customers = query.list(); for (Customer customer : customers) {

System.out.println(customer.getId() + \" \" + customer. getName());

for(Order order: customer.getOrders())

System.out.println(order.getNumber()); System.out.println(\"--------------\"); } session.close(); } }

在运行HQLAssociations程序后,Hibernate将输出如下的SQL语句。

select customer0_.id as col_0_0_, customer0_.name as col_1_0_, orders1_. order_number as col_2_0_ from t_customers customer0_ inner join t_orders orders1_ on customer0_.id=orders1_.customer_id -------------------------------

select customer0_.id as id3_0_, orders1_.id as id4_1_, customer0_.name as name3_0_, orders1_.order_number as order2_4_1_, orders1_.customer_id as customer3_4_1_ from t_customers customer0_ inner join t_orders orders1_ on customer0_.id=orders1_.customer_id ------------------------------

select customer0_.id as id3_0_, orders1_.id as id4_1_, customer0_.name as name3_0_, orders1_.order_number as order2_4_1_, orders1_.customer_id as customer3_4_1_, orders1_.customer_id as customer3_0__, orders1_.id as id0__ from t_customers customer0_ inner join t_orders orders1_ on customer0_. id=orders1_.customer_id order by orders1_.order_number asc

20.2.10 聚合函数

在HQL中也支持类似SQL中的聚合函数。HQL支持的聚合函数如下所示。 󰂉 avg(...):求属性的平均值 󰂉 sum(...):求属性值之和 󰂉 min(...):求属性的最小值 󰂉 max(...):求属性的最大值

󰂉 count(... 或 * 或 distinct...):获得持久化对象数

select count(*) from Order

下面的HQL语句可获得订单总数。

·635·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

distinct关键字可以使重复值的属性不参与统计。如有100个Order对象,其中有10个Order对象和另一个Order对象有同一个客户编号,那么使用下面的HQL语句获得的Order对象数为90。如下面的HQL语句所示:

select count(distinct customer) from Order

与上面的HQL语句对应的SQL语句如下:

select count(distinct order0_.customer_id) as col_0_0_ from t_orders order0_, t_customers customer1_ where order0_.customer_id=customer1_.id

20.2.11 Update和Delete语句:更新持久化对象

在Query接口中提供了一个executeUpdate方法用于执行HQL的update和delete语句。executeUpdate方法的定义如下:

executeUpdate方法返回一个int值,表示更新或删除的持久化对象数。update和delete的使用方法和SQL中的update和delete类似。下面的HQL语句将Customer中值为bea的name属性更新成sun。

update Customer set name = 'sun' where name='bea' public int executeUpdate() throws HibernateException;

下面的HQL语句删除所有name属性值为“sun”的Customer。

delete from Customer where name='sun'

下面代码是一个更新和删除持久化对象的完整示例。

public class HQLUpdateDelete {

public static void main(String[] args) {

chapter19.OneToMany.main(args); // 调用OneToMany类的main 方法向表中添加记录 Session session = HibernateSessionFactory.getSession(); Transaction tx = session.beginTransaction(); // 开始一个新事务 // 定义更新Customer对象的HQL语句

String hql = \"update Customer set name = 'sun' where name='bea'\"; Query query = session.createQuery(hql); // 根据hql语句创建一个 Query对象 int count = query.executeUpdate(); // 执行update语句 tx.commit(); // 提交事务 System.out.println(count); // 定义删除Customer对象的HQL语句

hql = \"delete from Customer where name='sun'\"; tx = session.beginTransaction(); // 开始一个新事务 query = session.createQuery(hql); count = query.executeUpdate(); // 执行delete语句 tx.commit(); // 提交事务 System.out.println(count); session.close(); // 关闭Hibernate ·636 ·

第20章 Hibernate的查询与更新技术

Session对象 } }

在HQLUpdateDelete类中调用了OneToMany向t_customers和t_orders表中插入了一些记录。然后使用update和delete子句更新或删除表t_customers中的记录。

󰀈注意:在执行

update和delete语句修改表中数据时,需要使用Session接口的

beginTransaction方法来开始事务,并使用Transaction的commit方法提交事务,否则,Hibernate不会真正修改数据库中的数据。

20.2.12 Insert语句:插入记录

HQL也支持Insert语句。在HQL中的Insert语句的语法如下:

从上面的Insert语句的语法可以看出,在HQL中的Insert语句并不支持values,要想指定要插入的值,必须使用select语句。下面代码是一个演示Insert语句用法的完整示例。

INSERT INTO EntityName properties_list select_statement

public class HQLInsert { public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); Transaction tx = session.beginTransaction();

String hqlDelete = \"delete MyMessage where id=20\"; // 定义delete 语句 Query query = session.createQuery(hqlDelete); // 删除id=20的MyMessage对象 query.executeUpdate(); // 执行delete语句

String hql = \"insert into MyMessage(id,name) select 20, name from MyMessage where id = 1\";

// 向t_message表中插入一条记录(id=20) query = session.createQuery(hql);

int count = query.executeUpdate(); // 执行insert语句 tx.commit(); // 提交事务 System.out.println(count); session.close(); // 关闭Hibernate Session对象 } }

在上面的代码中首先删除了t_message表中id等于20的记录。然后使用insert语句向t_message表中插入了一条id等于20的记录,name属性值是id等于1的MyMessage对象的name属性值。

在执行HQLInsert程序后,Hibernate将生成如下两条SQL语句。

delete from t_message where id=20

insert into t_message ( id, name ) select 20 as col_0_0_, mymessage0_.name as col_1_0_ from t_message mymessage0_ where mymessage0_.id=1

·637·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

20.2.13 命名查询

HQL的一个重要的特性就是命名查询。在HQL中允许将HQL和SQL语句保存在映射文件中,并在程序中执行映射文件中的HQL或SQL语句,这样做至少有如下3点好处:

󰂉 可以共享映射文件中的HQL和SQL语句。

󰂉 开发人员在获得命名查询时并不需要知道是HQL还是SQL,这将更有利于将基于

SQL的应用程序移植到Hibernate上。

󰂉 保存在映射文件中的HQL和SQL要比在硬编码到Java中的HQL和SQL更容易

维护。

在WEB-INF\\classes目录中建立一个query.xml映射文件,该文件用来保存HQL(在

标签中定义)和SQL(在标签中定义)语句。query.xml文件的代码 如下:

\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">

要想使用query.xml文件,必须在hibernate.cfg.xml中指定该文件的位置,代码如下:

通过Session接口的getNamedQuery方法可以获得命名查询,该方法的定义如下:

queryName属性表示查询名,也就是标签的name属性值。使用命名查询的代码如下:

public class HQLNamedQuery {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); Query query = session.getNamedQuery(\"myhql\"); // 通过命名参数myhql创建Query对象 query.setString(\"name\ // 设置命名查询的参数值 List messages = query.list(); // 执行命名查询 for (String name : messages) {

System.out.println(name); }

public Query getNamedQuery(String queryName) throws HibernateException;

·638 ·

第20章 Hibernate的查询与更新技术

// 通过命名参数mysql创建Query对象 // 获得mysql命名查询对象

query = session.getNamedQuery(\"mysql\"); query.setString(\"name\ // 设置name命名参数的值 messages = query.list(); // 执行命名查询 for (String name : messages) {

System.out.println(name); }

session.close(); // 关闭Hibernate Session对象 } }

从上面的代码可以看出,使用getNamedQuery方法既可以获得HQL的命名查询,也可以获得SQL的命名查询,而且该方法返回的是Query对象。由于执行SQL语句是由SQLQuery对象(将在20.2.14节介绍)完成的,而SQLQuery是Query类的子类,因此,getNamedQuery方法返回的SQL命名参数的Query对象也可以执行SQL语句。实际上,如果使用getNamedQuery方法获得SQL命名参数,返回的是SQLQuery对象,读者可以使用下面的代码输出Query对象的类名。

在执行上面的代码后,将输出如下的信息:

System.out.println(session.getNamedQuery(\"mysql\").getClass());

class org.hibernate.impl.SQLQueryImpl

其中SQLQueryImpl是实现SQLQuery接口的类。

20.2.14 使用SQL

虽然HQL可以完成大多数的数据库操作,但HQL并不支持数据库的所有特性,因此,在某些时候就需要直接使用SQL来操作数据库。

在HQL中使用org.hibernate.SQLQuery对象来执行SQL语句,SQLQuery对象实例由Session的createSQLQuery方法创建。createSQLQuery方法的定义如下:

其中everyString表示SQL语句。如果想让SQL语句查询出来的结果映射到持久化对象中,需要使用SQLQuery接口的addEntity方法指定相应的实体Bean。addEntity方法的定义如下:

// 如果SQL语句中只有一个表,可以使用这个重载形式

public SQLQuery addEntity(Class entityClass);

public SQLQuery addEntity(String alias, Class entityClass);

public SQLQuery createSQLQuery(String queryString) throws Hibernate- Exception;

其中entityClass表示实体Bean的Class对象,alias表示SQL语句中表的别名。如果不使用addEntity方法指定实体Bean,Hibernate会将用SQL查询出来的数据映射成List,每一个Object[]对象的元素就是当前记录的字段值。

在SQL中使用聚合函数产生数值结果时,一般需要使用SQLQuery的addScalar方法指定字段的类型,如下面的代码统计了t_message表中的记录数,并以Long类型返回结果。

·639·

Java Web开发技术大全——JSP+Servlet+Struts 2+Hibernate+Spring+AJAX

String sql = \"select count(*) as c from t_message\"; SQLQuery sqlQuery = session.createSQLQuery(sql); sqlQuery.addScalar(\"c\ // 指定记录数以Long类型返回 Long count = (Long)sqlQuery.uniqueResult();

下面代码是一个演示在Hibernate中使用SQL语句的完整示例。

public class NativeSQL {

public static void main(String[] args) {

Session session = HibernateSessionFactory.getSession(); String sql = \"select * from t_message m\"; // 定义SQL语句 SQLQuery sqlQuery = session.createSQLQuery(sql); // 使用addEntity方法将查询结果映射到MyMessage对象中 sqlQuery.addEntity(\"m\ List messages = sqlQuery.list(); for (MyMessage message : messages) {

System.out.println(message.getId() + \" \" + message.getName()); }

// 定义使用关联查询的SQL语句

sql = \"select c.*, o.order_number as n from t_customers c , t_orders o where c.id = o.customer_id\";

sqlQuery = session.createSQLQuery(sql); List result = sqlQuery.list(); for (Object[] obj : result) {

for (int i = 0; i < obj.length; i++) System.out.print(obj[i] + \" \"); System.out.println(); }

sql = \"select count(*) as c from t_message\"; // 定义使用聚合函数 的SQL语句 sqlQuery = session.createSQLQuery(sql);

sqlQuery.addScalar(\"c\ // 定义聚合后的结果 为Long型 Long count = (Long)sqlQuery.uniqueResult(); System.out.println(count); session.close(); } }

20.3 小 结

本章介绍了Hibernate框架支持的查询和更新技术。在Hibernate框架中可以使用标准查询API、HQL和SQL来查询数据。其中标准查询API是一套符合Java语法的查询API,而HQL和SQL在语法上非常类似,但HQL查询的是持久化对象,而SQL查询的是表。

如果想更新数据库中的记录,可以使用HQL和SQL的insert、delete和update语句对数据表的记录进行增、删、改操作。除此之外,HQL还支持很多高级特性,如排序和分组、关联查询、聚合函数等。为了使程序更容易维护,也可以使用命名查询将HQL或SQL保存在映射文件中,以供程序调用。

·640 ·

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 版权所有