Hibernate的检索方式
一、Hibernate检索方式简介
Hibernate提供以下几种检索对象的方式:
- 导航对象图检索方式。(根据已经加载的对象,导航到其他对象。)
-
OID检索方式。(按照对象的OID来检索对象。)
-
HQL检索方式。(使用面向对象的HQL查询语言。)
-
QBC检索方式。(使用QBC(Query By Criteria)API来检索对象)
-
本地SQL检索方式。(使用本地数据库的SQL查询语句。)
1、HQL检索方式
HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相识。在Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。它具有以下功能:
l 在查询语句中设定各种查询条件。
l 支持投影查询,即仅检索出对象的部分属性。
l 支持分页查询。
l 支持连接查询。
l 支持分组查询,允许使用having和group by关键字。
l 提供内置聚集函数,如sum()、min()和mac()。
l 能够调用用户定义的SQL函数。
l 支持子查询,即嵌入式查询。
l 支持动态绑定参数。
实例代码:
Query query = session.createQuery("from Customer as c where c.name=:customerName and c.age=:customerAge"); // 动态绑定参数 query.setString("customerName", "Test"); query.setInteger("customerAge", 21); // 执行检索 List result = query.list();
尽管HQL与SQL的语法形式上形似,但是他们并非神似:
-
HQL查询语句是面向对象的,Hibernate根据配置文件负责解析成SQL语句。
-
SQL查询语句直接与关系数据库打交道。
2、QBC检索方式
采用HQL检索方式时,在应用程序中需要定义基于字符串形式的HQL查询语句。QBC API提供了检索对象的另一种方式,它主要由Criteria接口、criterion接口和Restrictions类组成,它支持在运行时动态生成查询语句。
示例代码:
//创建一个Criteria对象 Criteria criteria = session.createCriteria(Customer.class); //设定查询条件,然后把查询条件加入Criteria中 Criterion criterion1 = Restrictions.like("namr", "T%"); Criterion criterion2 = Restrictions.eq("age", new Integer(21)); criteria = criteria.add(criterion1); criteria = criteria.add(criterion2); // 执行检索 List result = criteria.list(); // 方法链编程风格 List result1 = session.createCriteria(Customer.class).add(Restrictions.like("namr", "T%")).add(Restrictions.eq("age", new Integer(21))).
Hibernate还提供了QBE(Qurey By Example)检索方式,它是QBC的子功能。QBE允许先创建一个随想模板,然后检索出和这个样板相同的对象。
示例:(检索年龄为21的Customer对象)
Customer exampleCustomer = new Customer(); exampleCustomer.setAge(21); List result1 = session.createCriteria(Customer.class).add(Example.create(exampleCustomer)).list();
QBE的功能不是特别强大,仅在某些场合下有用。一个典型的使用场合就是在查询窗口中让用户输入一系列的查询条件,然后返回匹配的对象。QBE只支持“=”和“like”比较运算符,无法支持区间值,及其“或”匹配。在这种情况下,还是采用HQL检索方式或QBC检索方式。
3、本地SQL检索方式
采用HQL或QBC检索方式时,Hibernate生成标准的SQL查询语句,使用于所有的数据库平台,因此这两种检索方式都是跨平台的
有的应用程序可能需要根据底层数据库的SQL方言,来生成一些特殊的查询语句。在这种情况下,可以利用Hibernate提供的SQL检索方式。
示例代码:
Query query = session.createSQLQuery("select * from CUSTOMER as c where c.NAME like :customerName and c.AGE=:customerAge"); // 动态绑定参数 query.setString("customerName", "Test"); query.setInteger("customerAge", 21); // 执行检索 List result = query.list();
从以上程序可以看出,本地的SQL检索方式与HQL检索方式都使用Query接口,区别在于本地SQL检索方式通过Session的createSQLQuery()方法来创建SQLQuery对象。
4、对查询结果进行排序
HQL和QBC都支持对查询结果进行排序。
//HQL检索方式 Query query = session.createQuery("from Customer as c order by c.name asc,c.age desc");//姓名升序、年龄降序 //QBC检索方式 Criteria criteria=session.createCriteria(Customer.class); criteria.addOrder(Order.asc("name")); criteria.addOrder(Order.desc("age"));
5、分页查询
当批量查询的结果过多导致在单个页面上无法显示时,此时就需要对查询结果进行分页。介入CUSTOMER表中有99条记录,可以在用户终端上分10页来显示结果,每一页最多只显示10个Customer对象,用户即可以导航到下一页,也可以导航到上一页。
Query和Criteria接口都提供了用于分页显式查询结果的方法。
l setFirstResult(int firstResult):设置从哪一个对象开始检索,参数firstResult表示这个对象在查询结果中的索引位置,索引位置的起始值为0。默认从0检索。
l setMaxResult(int maxResults):设置一次最多检索出的对象数目。默认检索所有。
以下代码从查询结果的起始对象开始,共检索出10个Customer对象,查询结果对name属性排序:
//采用HQL检索方式 Query query = session.createQuery("from Customer c order by c.name asc"); query.setFirstResult(0); query.setMaxResults(10); List result=query.list(); //采用QBC检索方式 Criteria criteria=session.createCriteria(Customer.class); criteria.addOrder(Order.asc("name")); criteria.setFirstResult(0); criteria.setMaxResults(10); List result = criteria.list();
也可以采用链编程风格。
6、检索单个对象
Query和Criteria接口都提供了以下用于查询语句并返回查询结果的方法。
l list()方法:返回一个List类型的查询结果。
l uniqueResult()方法:返回单个对象。
注:Query接口还提供了一个iterate()方法,它和list()方法一样,能返回所有满足条件的持久化对象,但是两者使用不同的SQL查询语句。
示例代码:
// HQL单个检索 Customer customer = (Customer) session.createQuery("from Customer as c order by c.name").setMaxResults(1).uniqueResult(); // QBC单个检索 Customer customer = (Customer) session.createCriteria(Customer.class).setMaxResults(1).uniqueResult();
如果明明知道一个对象,可以不调用setMaxResults(1)方法。
7、按主键逐个处理查询结果(iterate()方法)
Query接口还提供一个iterate()方法,它和list()方法一样,也能执行查询操作。iterate()和list()的区别在于两者使用的查询机制不一样。
对于以下代码:
List c1=session.createQuery("from Customer c where c.age>10").list;
Iterator c2=session.createQuery("from Customer c where c.age>10").iterate();
第一行程序的list()执行的语句为:
select ID,NAME,AGE from CUSTOMERS where AGE>10;
第二行程序的iterate()执行的语句为:
select ID from CUSTOMERS where AGE>10;
使用方法:
Customer c1=(Customer)session.get(Customer.class,new Long(1)); Iterator c=session.createQuery("from Customer c where c.age>10").iterate(); while(c.hasNext()){ Customer customer=(Customer)customers.next(); System.out.println(customer.getName);}
大多数情况下会考虑使用Query的list()来执行查询,iterate()对查询性能的提高并不是很好。
8、可滚动的结果集
JDBC API提供了一种可滚动的结果集,它是利用DB system里的游标来实现的。游标用于定位查询结果中的记录,应用程序可以通过任意游标来定位到特定记录。
Query接口以及Criteria接口的scroll()方法返回一个org.hibernate.ScrollableResults对象,它代表可滚动的结果集。ScrollableResults接口包含以下用于移动游标的方法:
-
first()使游标移动到第一行
-
last()使游标移动到最后一行
-
beforeFirst()使游标移动到结果集的开头(第一行之前)
-
afterLast()使游标移动到结果集的末尾(最后一行之后)
-
previous()使游标从当前位置向上(或者说向前)移动一行
-
next()使游标从当前位置向下(或者说向后)移动一行
-
scroll(int n)使游标从当前位置移动n行(n>0,向下移动;n<0,向上移动)
-
setRowNumber(int n)使游标移动到行号为n的行。编号是从0开始的,n=-1时表示移动到最后一行
以上方法除了beforeFirst()和afterLast()返回void类型,其他都返回Boolean类型。
假设有这样一个表:
行号 | ID | NAME | AGE |
0 | 1 | TOM | 21 |
1 | 2 | MIKE | 24 |
2 | 3 | JACK | 23 |
3 | 4 | LINA | 52 |
4 | 5 | JEMMERY | 32 |
代码演示:
ScrollableResults rs=session.createQuery("from Customer c").scroll; //游标移动到结果集的第一行 rs.first(); Object[] o=rs.get(); Customer customer=(Customer)o[0];//获取对象数组的第一个对象 System.out.println(customer.getId()); rs.scroll(2);//游标从当前的位置移动2行 Customer customer=(Customer)rs.get(0);//获取当前行中的第一个字段,为Customer对象 System.out.println(customer.getId()); rs.close();
ScrollableResults接口在分页处理数据时作用很大,可以查看自己写的分页处理代码
final int PAGE_SIZE=3;//每页处理三个Customer对象 List firstNameOfPages=new ArrayList();//存储所有页中的第一个客户的姓名 List pageOfCustomers=new ArrayList();//存放第一页的所有Customer对象 ScrollableResults rs=session.createQuery("select c.name ,c from Customer c").scroll(); if(rs.first()){ //获取所有页中的第一个客户的姓名 do{ String name=rs.getString(0);//获取当前行的第一个字段,为name字段 firstNameOfPages.add(name); }while(rs.scroll(PAGE_SIZE)); //获取第一页中的所有Customer对象 rs.beforeFirst(); int i=0; while((PAGE_SIZE>i++)&&rs.next()){ pageOfCustomers.add(rs.get(1));//获取当前行的第二个字段,为Customer对象 } rs.close(); for(int i=0;i<firstNameOfPages.size();i++){System.out.println(firstNameOfPages.get(i));} for(int i=0;i<pageOfCustomers.size();i++){}System.out.println(pageOfCustomers.get(i));}
9、在HQL查询语句中绑定参数
在实际的应用中,经常会有用户在查询窗口中输入一些查询条件,要求返回满足条件的记录。如下:
public List findCustomers(String name,int age){ Session session=getSession(); Query query=session.createQuery("from Customer as c where c.name='"+name+"'and c.age='"+age+"'");}
虽然以上的代码可以实现,但是不安全。为什么呢?假如有一个恶意的用户在姓名的输入框中输入以下的内容:
Tom' and SomeStoredProcedure() and 'hello' = 'hello
那么实际的HQL查询语句即为:
from Customer as c where c.name='Tom' and SomeStoredProcedure() and 'hello' = 'hello' and c.age=20
以上的查询语句不仅会执行查询数据库,而且还会执行一个名为“SomeStoredProcedure”的存储过程。Hibernate采取绑定参数的方式来解决这个问题。
参数绑定机制有以下优点:
- 安全
- 能够利用底层数据库预编译SQL语句的功能,提高查询数据的性能。
1、参数绑定的形式
(1)按参数名字绑定
- Query query=session.createQuery("from Customer as c where c.name:=customerName and c.age:=customerAge);
命名参数以“:”开头。
接下来调用Query的setXXX()方法来绑定参数:
- query.setString("customerName",name);
- query.setInteger("customerAge",age);
这些setXXX()方法第一个参数为命名参数的名字,第二个参数为命名参数的值。
假如有一个恶意的用户在姓名的输入框中输入以下的内容:
Tom' and SomeStoredProcedure() and 'hello' = 'hello
Hibernate就会解析成:
- from Customer as c where c.name='Tom'' and SomeStoredProcedure() and ''hello' = 'hello' and c.age=20
(2)按参数位置绑定
在HQL查询语句中用“?”来定义参数的位置,形式如下:
- Query query=session.createQuery("from Customer as c where c.name=? and c.age=?);
按位置绑定:
- query.setString(0,name);
- query.setInteger(1,age);
2、绑定各种类型的参数
Query接口提供了绑定各种Hibernate映射类型的参数方法,如下:
- setBinary()绑定映射类型为binary的参数
- setBoolean(),setByte(),setCalender(),setCharacter(),setDate(),setDouble(),setString(),setText(),setTime(),setTimestamp()
Hibenate还提供三种特殊的参数绑定方法:
- setEntity():把参数与一个持久化类的实例绑定
- setParameter():绑定任意类型的参数
- setProperties():用于把命名参数与一个对下岗的属性值绑定
10、设置查询附属事项
在采用HQL检索方式或者QBC检索方式来检索数据时,可以通过Query或者Criteria接口的一些方法来设定查询附属事项:
- setFlushMode()方法:设置清理缓存的模式
- setCacheMode()方法:设置Session与二级缓存的交互模式
- setTimeout()方法:设置执行查询数据库操作的超时时间
- setFetchSize()方法:为JDBC驱动程序设置批量抓取的数目
- setLockMode()方法:设定锁定模式
- setCommet()方法:为SQL日志设置注解
11、在HQL查询语句中调用函数
HQL中可以调用SQL函数。而且还可以调用专门的HQL函数。
一、连接查询
与SQL一样,HQL与QBC也支持各种各样的连接查询。如下表,
1、迫切左外连接查询
显式指定与Customer关联的Order对象采取迫切左外连接检索策略:
//HQL List result=session.createQuery("from Customer c left join fetch c.orders o where c.name like 'T%'").list(); for(Iterator it=result.iterator;it.hasNext();){ Customer customer=(Customer)it.next;} //QBC List result=session.createCriteria(Customer.class).setFetchMode("orders",FetchMode.JOIN).add(Restrictions.like("name","T",MatchMode.START)).list(); for(Iterator it=result.iterator;it.hasNext();){ Customer customer=(Customer)it.next;}
以上代码生成的SQL查询语句为:
select c.ID C_ID,c.NAME,c.AGE,o.ID O_ID,o.ORDER_NUMBER,o.CUTOMER_ID from CUSTOMERS c left outer join OEDERS o on c.ID=o.CUSTOMER_ID where (c.NAME like 'T%');
查询结果如下,
2、左外连接
查询语句为:
//HQL List result=session.createQuery("from Customer c left join c.orders where c.name like 'T%'").list(); for(Iterator pairs=result.iterator;pairs.hasNext();) { Object[] pair=(Object[])pairs.next(); Customer customer=(Customer)pair[0]; Order order=(Order)pair[1]; //如果orders集合采用延时检索策略,以下代码会初始化Customer对象的orders集合 customer.getOrders().iterator(); }
使用左外连接查询时,根据映射文件的配置来决定orders集合的检索策略。不过,即使在Customer.hbm.xml文件中对orders集合设置了延迟检索策略,在运行以上的Query的list()方法时,Hibernate执行的SQL查询语句仍然和迫切左外连接查询生成的SQL查询语句相同。
3、内连接
inner join表示内连接。
//HQL List result=session.createQuery("from Customer c inner join c.orders where c.name like 'T%'").list(); for(Iterator pairs=result.iterator;pairs.hasNext();) { Object[] pair=(Object[])pairs.next(); Customer customer=(Customer)pair[0]; Order order=(Order)pair[1]; //如果orders集合采用延时检索策略,以下代码会初始化Customer对象的orders集合 customer.getOrders().iterator(); }
假定在Customer.hbm.xml文件中对orders集合设置了延时检索策略,那么运行list()时,SQL语句为:
select c.ID C_ID,c.NAME,c.AGE,o.ID O_ID,o.ORDER_NUMBER,o.CUTOMER_ID from CUSTOMERS c inner join OEDERS o on c.ID=o.CUSTOMER_ID where (c.NAME like 'T%');
以上查询结果为:
Select * from ORDERS where CUSTOMER_ID=1;
QBC也支持内连接查询,如:
Criteria customerCriteria=session.createCriteria(Customer.class); customerCriteria.add(Restrictions.like("name","T",MatchMode.START)); Criteria orderCriteria=session.createCriteria(Order.class); orderCriteria.add(Restrictions.like("orderNumber","T",MatchMode.START)); List result=orderCriteria.list(); //支持链编程方式 List result=session.createCriteria(Customer.class) .add(Restrictions.like("name","T",MatchMode.START)) .createCriteria(Order.class) .add(Restrictions.like("orderNumber","T",MatchMode.START)) .list();
在默认情况下只检索出Customer对象,因此可采用以下方式访问result集合中所有Customer对象,
for(Iterator pairs=result.iterator();pairs.hasNext();) { Customer customer=(Customer)pairs.next(); //如果orders集合采用延时检索策略,以下代码会初始化Customer对象的orders集合 Iterator orders=customer.getOrders().iterator(); ....... }
此外,Criteria的createAlias()方法也可以完成相同的功能,
List result=session.createCriteria(Customer.class) .createAlias("orders","o") .add(Restrictions.like("name","T",MatchMode.START)) .add(Restrictions.like("o.orderNumber","T",MatchMode.START)) .list();
createAlias()为orders集合赋予别名o,因此可以以o.orderNumber俩访问Order类中的orderNumber属性。在以上的程序中,Customer类的默认别名为this,因此上述代码也可以改成,
List result=session.createCriteria(Customer.class) .createAlias("orders","o") .add(Restrictions.like("this.name","T",MatchMode.START)) .add(Restrictions.like("o.orderNumber","T",MatchMode.START)) .list();
采用内连接查询时,HQL与QBC用不同的默认行为,HQL默认检索出成对的Customer对象和Order对象,而QBC只检索出Customer对象。如果希望QBC返回Customer和Order对象,可以调用Criteria的setResultTransform()方法,
List result=session.createCriteria(Customer.class) .createAlias("orders","o") .add(Restrictions.like("name","T",MatchMode.START)) .add(Restrictions.like("o.orderNumber","T",MatchMode.START)) .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP) .list(); for(Iterator pairs=result.iterator;pairs.hasNext();) { Map pair=(Map)pairs.next(); Customer customer=(Customer)map.get("this"); Order order=(Order)map.get("o"); ...... }