HQL查询语言(重点)

来源:互联网 发布:人工智能最新英文新闻 编辑:程序博客网 时间:2024/05/30 02:23

 

示例: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语句放入到一个配置文件中在调试时,会增加一定的工作量,有时显得会不太方便,当项目追求低耦合度时,可以考虑采用这种方式。

 

实体映射文件:

在</class>的标签后面加入如下代码:

<!-- 外置命名查询定义 -->

<query name="searchUsers">

    <![CDATA[

                SELECT u FROM User u where u.id<?

     ]]>

</query>

说明: 配置文件中之所以使用<![CDATA[… ]]>是因为在HQL语句中含有”<”号,该符合是XML语言的组成部分,如果文本中含有XML语言特定的符号时可以将文本放在<![CDATA[… ]]> 中,如果不使用<![CDATA[… ]]>,则这些特定符号需要进行转义,否则会引起一些问题。

 

使用:

List users = session.getNamedQuery("searchUsers")

.setParameter(0, 6)

.list();

说明:

    searchUsers:为配置文件中外置命名查询的名称

    setParameter(0, 6):为外置命名查询的HQL语句的占位符赋值

在映射文件中采用<query>标签来定义HQL语句

在程序中采用session.getNamedQuery()方法得到HQL查询串

 

HQL语言-查询过滤器:

启用查询过滤器后,当Session没有关闭的时候,只要在Session中执行的所有HQL语句都会自动加上查询过滤器中的条件(即实现过滤功能)。

 

(1)定义查询过滤器

在</class>的标签后面加入如下代码:

    <filter-def name="filtertest">

        <filter-param name="myid" type="integer"/>

    </filter-def>

说明:

filtertest:查询过滤器名称

myid:过滤条件中的参数名称

type:指定参数的类型,本例为小写的integer表示这是Hibernate中的类型,也可以写成Java中的java.lang.Integer

 

(2)为实体映射文件指定使用查询过滤器

在<class>标签中的最后加入如下代码:

    <!-- 指定使用查询过滤器,i_id数据库字段名 -->

    <filter name="filtertest" condition="i_id &lt; :myid"/>

说明:

name:指定要使用的查询过滤器名称

condition:指定查询过滤器的条件,将来过滤器将使用该条件进行过滤操作。其中“&lt; ”表示的是“<”号,因为该符号是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 whereu.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("selectcount(id)fromUser").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() 方法可以指定要从缓存中清除哪个实体对象