Hibernate 3.x——HQL查询语言
示例:Hibernate3_10_HQL
Hibernate查询:
数据查询与检索是Hibernate中的一个亮点。相对其他ORM实现而言,Hibernate提供了灵活多样的查询机制。
标准化对象查询(Criteria Query):以对象的方式进行查询,将查询语句封装为对象操作。优点:可读性好,符合Java 程序员的编码习惯。缺点:不够成熟,不支持投影(projection)或统计函数(aggregation)
Hibernate语言查询(Hibernate Query Language,HQL):它是完全面向对象的查询语句,查询功能非常强大,具备多态、关联等特性。Hibernate官方推荐使用HQL进行查询。
Native SQL Queries(原生SQL查询):直接使用标准SQL语言或跟特定数据库相关的SQL进行查询。
Hibernate语言查询(Hibernate Query Language,HQL):
HQL用面向对象的方式生成SQL语句:
以类和属性来代替表和数据列;
支持多态;
支持各种关联;
减少了SQL的冗余;
HQL支持所有的关系数据库操作:
连接(joins,包括Inner/outer/full joins),笛卡尔积(cartesian products);
投影(projection)--查询一到多个属性;
聚合(Aggregation,max, avg)和分组(group);
排序(Ordering);
子查询(Subqueries);
SQL函数(SQL function calls)
简单示例:查询用户名以“王”开头的所有用户。
Query query = session.createQuery(
"from User user where user.name like '王%'");
List users = query.list();
复杂示例:从User和Group中查找属于“admin”组的所有用户。
Query query = session.createQuery(
"from User user where user.group.name='admin'");
如果用传统的SQL则查询语句如下:
select user.userId as userId, user.name as name, user.groupId as groupId, user.idCardId as idCardId from TBL_USER user, TBL_GROUP group where (group.groupName='admin' and user.groupId=group.groupId)
HQL语言-简单属性查询:
(1)单一属性查询:
List users = session.createQuery("select name from User").list();
说明:
List集合中此时存储的是name属性集合,其类型为实体类(即User)的name属性的类型(本例为String类型),所以List集合中对象的类型取决于实体类中属性的类型。
(2)多属性查询:
List users =
session.createQuery("select id, name from User ").list();
说明:
List集合中此时存储的对象数组(即Hibernate查询多个属性时返回的是对象数组,一条记录为一个对象数组,最后将所有对象数组存储到集合中),其中对象数组的长度与查询的属性个数是相同的(即select关键字后面有多少个属性,将来对象数组的长度就是多少);
为什么会是Object类型的数组?这是因为实体类中属性的类型是不同的,所以用Object类型表示更为通用。
数组下标元素存储的对象类型与实体类中属性的类型相同。
(3)采用HQL动态实例化对象方式返回所要查询的属性:
List users =
session.createQuery("select new User(id, name) from User").list();
说明:
List集合中此时存储的是User对象,但是该User对象并不是实体对象,而是我们自己new出来的(即不是Hibernate自动封装的),因此不受Session统一管理,此时的User对象中只有id和name属性有值,其它属性均为null。
User对象必须提供一个构造函数,形式为:
public User(int id, String name) {…}
否则将不能创建User对象,因为创建对象时调用的是带参数的构造方法。
(4)采用别名的方式查询属性值:
采用别名方式查询属性值,在Hibernate中可以使用as关键字指定别名,也可以不使用as关键字指定别名,两者都可以指定别名,如下所示:
不采用as关键指定别名
List users =
session.createQuery("select u.id, u.name from User u").list();
采用as关键字指定别名
List users =
session.createQuery("select u.id, u.name from User as u").list();
说明:当指定别名后,在查询的属性名需要使用前缀时必须使用别名,而不能再使用原实体对象的名称,即: u.id, u.name。
【小结】
单一属性查询,返回结果集属性列表,元素类型和实体类中相应的属性类型一致;
多个属性查询,返回的集合元素是对象数组,数组元素的类型和对应的属性在实体类中的类型一致;
数组的长度取决与select中属性的个数;
如果认为返回数组不够对象化,可以采用HQL动态实例化对象的方式:
select new User(id, name) from User
HQL语言-实体对象查询:
查询实体对象
List students = session.createQuery("from User").list();
说明:
此时List集合中存储的User对象为持久状态,并纳入了Session的管理,当User对象的属性发生变化时,在提交事务后会发出相应的update语句与数据库同步。
如:
if(user.getId() == 1){
user.setName("AAAA");
//会发出update语句,因为此时的user对象是实体对象,由于名字发生了变化,所以此时在提交事务后会发出相应的update语句与数据库同步
session.getTransaction().commit();
}
使用别名查询实体对象-忽略select关键字
采用别名方式查询属性值,在Hibernate中可以使用as关键字指定别名,也可以不使用as关键字指定别名,两者都可以指定别名,如下所示:
不采用as关键指定别名
List users = session.createQuery("from User u").list();
采用as关键字指定别名
List users = session.createQuery("from User as u").list();
使用别名查询实体对象-不省略select关键字
当使用select关键字查询实体对象时,必须使用别名的方式,如下所示:
不采用as关键指定别名
List users = session.createQuery("select u from User u").list();
采用as关键字指定别名
List users = session.createQuery("select u from User as u").list();
注:不能使用select * from User来进行查询
(1)使用list()方法查询数据:
使用list()方法会发出一条SQL语句查询数据,但是每调用一次list()方法都会发出SQL语句去查询数据。
如:
List users = session.createQuery("from User").list();
说明: list()方法只会把查询出来的数据放入Session(一级)缓存中,而不使用一级缓存中的数据(缓存中存在相同数据时,则不会再放)。
(2)使用iterate()方法查询数据:
使用iterate()方法有时会发出N+1条SQL语句查询数据,其中:
1: 首先发出一条查询对象id列表的sql,这条SQL语句,只要调有了iterate()方法就会发出。
N: 根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的SQL语句查询数据,如果缓存中有与之匹配的数据,则直接使用缓存中的数据,就不会再发出SQL语句。
如:
Iterator iter =
session.createQuery("from User").iterate();
说明: iterate()方法总会首先发出一条查询对象id列表的sql 语句,然后根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的SQL语句查询数据。这说明iterate()方法使用缓存。
【小结】
N + 1问题,在默认情况下,使用query.iterate()查询,有可以能出现N+1问题。
所谓的N+1是在查询的时候发出了N+1条SQL语句:
1: 首先发出一条查询对象id列表的SQL语句;
N: 根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的SQL语句。
list()方法和iterate()方法的区别?
list()方法:每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据;
iterate()方法:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题。
HQL语言-条件查询:
(1)采用拼接字符串形式查询
如:
String hql = "select u.id, u.name from User u where u.name like ";
String key = "'%张%'";
hql = hql + key;
List user = session.createQuery(hql).list();
(2)采用占位符的形式查询
如:
Query query = session.createQuery("select u.id, u.name from User u where u.name like ?");
query.setParameter(0, “%王%");
List users = query.list();
或使用方法链编程:
List users = session.createQuery("select u.id, u.name from User u where u.name like ?")
.setParameter(0, “%王%")
.list();
注:在Hibernate中第1个占位符的下标位置从0开始
(3)采用参数命名的方式查询
单一条件
List users = session.createQuery("select u.id, u.name from User u where u.name like :myname")
.setParameter("myname", "%张%")
.list();
多条件
List users = session.createQuery("select u.id, u.name from User u where u.name like :myname and u.id=:myid")
.setParameter("myname", "%张%")
.setParameter("myid", 1)
.list();
说明:定义命名参数固定格式::+参数名称(即:myid )
赋值时,直接写参数名即可: setParameter("myid", 1)
setParameter方法用于为参数赋单一值
(4)采用in关键字(即包含)查询
List users = session.createQuery("select u.id, u.name from User u where u.id in(:myids)")
.setParameterList("myids", new Object[]{1, 2, 3, 4, 5})
.list();
说明:由于in中的条件是集合的形式,所以赋值是需要采用setParameterList这种方法,赋的值则采用对象数组的形式。
(5)采用数据库函数查询——不建议使用
在Hibernate的HQL语言中可以直接使用数据库中的函数,但是这就使Hibernate与特定的数据库进行了高耦合,不便于日后的移植。
如:
List students = session.createQuery("select u.id, u.name from User u where CONVERT(varchar(10), DATEPART(mm,u.create_time))=?")
.setParameter(0, "06")
.list();
(6) 查询某一时间段的数据
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//查询2010-06-10到2010-07-31入职的员工
List users = session.createQuery("select u.id, u.name from User u where u.create_time between ? and ?")
.setParameter(0, sdf.parse("2010-06-10 00:00:00"))
.setParameter(1, sdf.parse("2010-07-31 23:59:59"))
.list();
说明:在实际项目开发中,关于日期段的查询一般都是从起始日期的00:00:00时间开始,结束于结束日期的23:59:59,这样对于日期类型的数据库表的字段更为精确。
【小结】
可以采用拼字符串的方式传递参数
可以采用 ?来传递参数(索引从0开始)
可以采用 :参数名 来传递参数
如果传递多个参数,可以采用setParamterList方法
在HQL语言中可以使用数据库的函数,如:CONVERT、DATEPART
HQL语言-原生SQL语句查询:
采用原生SQL语句查询
List users = session.createSQLQuery("select * from t_user").list();
说明:
使用原生SQL语句,需要使用:SQLQuery接口;
使用原生SQL语句时,不会往Session(一级)缓存中放入数据,即SQLQuery接口中的list()方法不会往Session(一级)缓存中放入数据;
SQLQuery接口的list()方法返回的List集合中存储的是数组对象
HQL语言-外置命名查询:
外置命名查询就是指把HQL语句放到一个配置文件中,而不是把HQL语句直接写到程序代码中,采用这种方式的目的是为了让HQL语句与程序代码更好的进行解耦。
在实际开发中这并不是决对的,因为当把所有的HQL语句放入到一个配置文件中在调试时,会增加一定的工作量,有时显得会不太方便,当项目追求低耦合度时,可以考虑采用这种方式。
实体映射文件:
在的标签后面加入如下代码:
SELECT u FROM User u where u.id
]]>
说明: 配置文件中之所以使用是因为在HQL语句中含有”<”号,该符合是XML语言的组成部分,如果文本中含有XML语言特定的符号时可以将文本放在 中,如果不使用,则这些特定符号需要进行转义,否则会引起一些问题。
使用:
List users = session.getNamedQuery("searchUsers")
.setParameter(0, 6)
.list();
说明:
searchUsers:为配置文件中外置命名查询的名称
setParameter(0, 6):为外置命名查询的HQL语句的占位符赋值
在映射文件中采用
在程序中采用session.getNamedQuery()方法得到HQL查询串
HQL语言-查询过滤器:
启用查询过滤器后,当Session没有关闭的时候,只要在Session中执行的所有HQL语句都会自动加上查询过滤器中的条件(即实现过滤功能)。
(1)定义查询过滤器
在的标签后面加入如下代码:
说明:
filtertest:查询过滤器名称
myid:过滤条件中的参数名称
type:指定参数的类型,本例为小写的integer表示这是Hibernate中的类型,也可以写成Java中的java.lang.Integer
(2)为实体映射文件指定使用查询过滤器
在
说明:
name:指定要使用的查询过滤器名称
condition:指定查询过滤器的条件,将来过滤器将使用该条件进行过滤操作。其中“< ”表示的是“<”号,因为该符号是XML语言的特殊符号,所以在这里需要进行转义。
(3)在程序中启动查询过滤器
//启用查询过滤器,只有启动以后查询过滤器才起作用
session.enableFilter("filtertest")
.setParameter("myid", 6);
【小结】
在映射文件中定义过滤器参数
在类的映射中使用这些参数
在程序中启用过滤器
查询过滤器中的条件在Hibernate生成具体的SQL语句时会自动加上该条件,所以条件中必须使用表中的字段名
HQL语言-分页查询:
Hibernate分页查询
List users = session.createQuery("from User")
//指定从第几条记录开始,下标从0开始,本例为从第2条记录开始查询
.setFirstResult(1)
//每页显示多少条记录
.setMaxResults(2)
.list();
HQL语言-对象导航查询:
所谓的对象导航查询是指查询某一个实体时,条件是这个实体所关联的另外一个实体对象,例如:查询员工的姓名,条件是根据部门名称进行查询。
对象导航查询:
List users =
session.createQuery("select u.name from User u where u.dept.name like '%人力%'").list();
HQL语言的含义是:查找部门名称中含有“人力”字样的部门中的员工姓名字段。
说明:查询的条件是根据User类中的关联的实体对象dept的名称来进行查询的,其中dept是User类中对Dept的引用,那么这种导航的工作原理是根据配置文件中的关联映射完成的。
在HQL语句中采用 . 进行导航
HQL语言-连接查询:
Hibernate中支持3种连接查询:
内连接
左外连接
右外连接
说明: HQL中的连接必须采用对象导航进行连接,否则将抛出
org.hibernate.hql.ast.QuerySyntaxException异常。
(1)内连接
示例:查询员工的姓名及所在部门的名称
内连接有两种方式:
不使用inner关键字
List users = session.createQuery("select d.name, u.name from User u join u.dept d").list();
使用inner关键字
List users = session.createQuery("select d.name, u.name from User u inner join u.dept d").list();
说明: 在Hibernate中上述 的HQL语句就能够将User实体对象和Dept实体对象的关联关系建立起来了,因为这都是通过实体映射文件来完成的,体现在表上则是通过两张表的连接完成的。
(2)左外连接
示例:查询部门的名称(包含没有员工的部门)及员工的姓名
List users = session.createQuery("select d.name, u.name from Dept d left join d.users u").list();
说明: Hibernate会根据Dept实体类的映射文件中的关联映射关系自动发出左外连接的SQL语句(即t_dept表与t_user表之间采用左外连接的SQL语句)。
(3)右外连接
示例:查询员工姓名(包含未分配部门的员工)及员工所在部门的名称。
List users = session.createQuery("select d.name, u.name from Dept d right join d.users u").list();
说明:Hibernate会根据Dept实体类的映射文件中的关联映射关系自动发出右外连接的SQL语句(即t_dept表与t_user表之间采用右外连接的SQL语句)。
HQL语言-统计查询:
Hibernate 3.x支持统计查询,但是使用Hibernate做纯粹的统计应用比较少,因为涉及到的数据量非常大,在实际项目开发中,一般采用第三方的报表工具,比较常用的有:iReport、如意报表、JfreeChart等。
示例:查询员工人数
统计总数,一般有编程两种方式:
(1)采用获取返回的List集合长度的方式
List users = session.createQuery("select count(id) from User").list();
//Hibernate的统计返回值一般都是long型,防止统计数据过大造成越界异常
Long count = (Long)users.get(0);
说明:这种方式效率不高,因为返回的是一个List,而该List中就一个元素(即统计的数量值),然后还需要使用get方法将该元素取出。
(2)采用从返回的Long对象中获取统计数值
Long count =
(Long)session.createQuery("select count(id) from User").uniqueResult();
说明:这种方式效率高,因为返回的不是一个List集合,而是一个长整型的对象,该对象即是统计出来的数据,因为Hibernate使用长整型来封装统计出来的数值,目的是防止统计出来的数值过大,造成数据溢出异常。数据类型一定要切记,是Long(即封装类)。
HQL语言-DML查询:
Hibernate 3.x支持DML风格的操作,所谓DML风格就是指批量的执行insert、update、delete等操作这里的这些操作指的都是批量执行Hibernate中对应的save、update、delete方法。
但是这些批量操作一般不建议使用,因为当批量执行一批语句后,Hibernate是不会修改缓存的,这就造成了数据库中的数据与缓存中的数据不同步的问题,假设一次性批量插入1万条数据,这时Hibernate不可能将这1万条数据都放入缓存,因为这非常容易导致内存溢出异常。
在实际项目开发中对于那些变化不大数据( 即不是经常访问的数据)可以采用DML来操作。
示例:
session.createQuery("update User u set u.name=? where u.id < ?")
.setParameter(0, "XXX")
.setParameter(1, 5)
.executeUpdate();
说明:执行完批量的update操作后数据库中的值已经发生了变化,但是缓存中的值并没有发生变化还是原来的值,但是当我们访问时,其实访问的是缓存中的值,这就造成了数据的不准确性。
重点!!! |
注:
session.get()和session.load()区别:
get()方法:
1. 不支持延迟加载,会马上发出查询sql语句,加载User对象;
2. 使用get()方法加载数据,如果数据库中不存在相应的数据,则返回null。
load()方法:
1. 支持延迟加载,只有真正使用这个对象的时候,才会发出查询sql语句
2. 使用load加载数据,如果数据库中没有相应的数据,那么抛出ObjectNotFoundException,即不会返回null。
get(),load()方法会把查询上来的数据放入缓存,也会使用缓存
get(),load()方法会将查询上来的数据放入一级缓存中一份,然后再将该数据放入二级缓存中一份;先查找一级缓存再查找二级缓存,如果在任何一级缓存中能够查到该数据,则直接进行使用,否则发出SQL加载数据
结论:session是共享二级缓存(SessionFactory缓存)的
Query query = session.createQuery("")
query.iterate()
iterate() 查询实体对象:
iterate() 使用缓存(但不包括查询缓存,oid是存储在查询缓存中的),发出查询id的SQL语句,不会发出查询实体对象的SQL语句。
原理同load()方法,缓存中如果存在该实体对象,则直接使用,如果不存在该实体对象,则发出SQL语句去查询。
iterate() 方法查询普通属性:
一级缓存、二级缓存只存储实体对象
发出查询姓名属性的SQL语句;不会发出查询ID属性列表的SQL语句(因为此时的HQL语句查询的是普通属性而不是实体对象)
* 一(二)级缓存是缓存实体对象的,即如果查询出来的是实体对象,则才向一(二)级缓存中存放,如果查询出来的是普通属性,则不向一(二)级缓存中存放。
* 思考:执行如下语句,是否将查询出来的数据存放到一级缓存中?
Iterator iter = session.createQuery("select new User(u.name) from User u where u.id=1").iterate();
答案:不会向一级缓存中存放,因为此时查询出来的User对象是我们自己new出来的,而不是Hibernate自动为我们new出来的,所以不受Session管理。
Query.list()方法:
1、当关闭查询缓存(默认)时,list()方法不使用一级缓存和二级缓存中的数据
2、当开启查询缓存时,list()方法就会使用一级缓存和二级缓存中的数据,顺序是先找一级缓存再找二级缓存
Session 、SessionFactory:
clear() 方法清除缓存中的全部实体对象
evict() 方法可以指定要从缓存中清除哪个实体对象