HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:

  1. 在查询语句中设定各种查询条件;
  2. 支持投影查询, 即仅检索出对象的部分属性;
  3. 支持分页查询;
  4. 支持连接查询;
  5. 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字;
  6. 提供内置聚集函数, 如 sum(), min() 和 max();
  7. 支持子查询;
  8. 支持动态绑定参数;
  9. 能够调用 用户定义的 SQL 函数或标准的 SQL 函数。

HQL 查询包括以下步骤:

  1. 获取Hibernate Session对象。
  2. 编写HQL语句
  3. 以HQL语句作为参数,调用Session的createQuery方法创建查询对象。
  4. 如果HQL语句包含参数,则调用Query的setXxx方法为参数赋值。
  5. 调用Query对象的list()或uniqueResult()方法返回查询结果列表(持久化实体集)

Qurey 接口支持方法链编程风格, 它的 setXxx() 方法返回自身实例, 而不是 void 类型,因此可以写类似于.setXxx().setXxx().setXxx()...样式的语句。

HQL vs SQL


HQL 查询语句是面向对象的, Hibernate 负责解析 HQL 查询语句, 然后根据对象-关系映射文件中的映射信息, 把 HQL 查询语句翻译成相应的 SQL 语句。HQL 查询语句中的主体是域模型中的类及类的属性。

SQL 查询语句是与关系数据库绑定在一起的。SQL 查询语句中的主体是数据库表及表的字段。

HQL实用技术


实体查询

最简单实体查询例子:

String hql = "from User";
Query query = session.createQuery(hql);
List<User> list = query.list();

上面的HQL语句将取出User的所有对应记录为:select user0_.U_ID as U_ID1_0_,user0_.U_NAME as U_NAME2_0_,user0_.U_AGE
as U_AGE3_0_ from USERS user0_

在HQL语句中,本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。同时,在Hibernate中,查询的目标实体存在继承关系的判定,如果from
User
将返回所有User以及User子类的记录。假设系统中存在User的两个子类:SysAdmin和SysOperator,那么该hql语句返回的记录将包含这两个子类的所有数据,即使SysAdmin和SysOperator分别对应了不同的库表。

Where子句: 如果我们想取出名为“Erica”的用户记录,可以通过Where子句加以限定(其中AS可以省略):

FROM User AS user WHERE user.name='Erica'

where子句中,我们可以通过比较操作符指定甄选条件,如: =, , <, >, <=, >=, between, not between, in ,not in, is, like等。同时,在where子句中可以使用算术表达式。 几个简单实例:

FROM User user WHERE user.age<20
FROM User user WHERE user.name IS null
FROM User user WHERE user.name LIKE 'Er%'
FROM User user WHERE (user.age % 2 = 1)
FROM User user WHERE (user.age<20) AND (user.name LIKE '%Er')

属性查询

有时,我们需要的数据可能仅仅是实体对象的某个属性(库表记录中的某个字段信息)。通过HQL可以简单的做到这一点。

String hql = "SELECT user.name FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}

上例中,我们指定了只需要获取User的name属性。此时返回的list数据结构中,每个条目都是一个String类型的name数据。 我们也可以通过一条HQL获取多个属性:

public void test() {
String hql = "SELECT user.name,user.age FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
Object[] results = (Object[]) it.next();
System.out.println(results[0]+","+results[1]);
}
}

而此时,返回的list数据结构中,每个条目都是一个对象数组(Object[]),其中依次包含了我们所获取的属性数据。

除此之外,我们也可以通过在HQL中动态的构造对象实例的方法对这些平面化的数据进行封装。

String hql = "SELECT new User(user.uName,user.uAge) FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
User user = (User) it.next();
System.out.println(user);
}

通过在HQL中动态的构造对象实例,我们实现了对查询结果的对象化封装。此时在查询结果中的User对象仅仅是一个普通的Java对象,仅用于对查询结果的封装,除了在构造时赋予的属性值外,其他属性均为未赋值状态。同时,在实体类中,要提供包含构造属性的构造方法,并且顺序要相同。

与此同时,我们也可以在HQL的select子句中使用统计函数或者利用DSITINCT关键字,剔除重复记录。

SELECT COUNT(*),MIN(user.age) FROM User user
SELECT DISTINCT user.name FROM User user

实体更新与删除

String hql = "UPDATE User SET age = 18 WHERE id = 1";
int result = session.createQuery(hql).executeUpdate();

上述代码利用HQL语句实现了更新操作。对于单个对象的更新也许代码量并没有减少太多,但如果对于批量更新操作,其便捷性以及性能的提高就相当可观。 例如,以下代码将所有用户的年龄属性更改为10

UPDATE User SET age = 18

HQL的delete子句使用同样很简单,例如以下代码删除了所有年龄大于18的用户记录:

DELETE User WHERE age > 18

不过,需要注意的是,在HQL delete/update子句的时候,必须特别注意它们对缓存策略的影响,极有可能导致缓存同步上的障碍。

分组与排序

Order by子句

举例说明:

FROM User user ORDER BY user.name

默认情况下是按照升序排序,当然我们可以指定排序策略:

FROM User user ORDER BY user.name DESC

order by子句可以指定多个排序条件:

FROM User user ORDER BY user.name, user.age DESC

Group by子句

通过Group by可进行分组统计,如果下例中,我们通过Group by子句实现了同龄用户的统计:

SELECT COUNT(user),user.age FROM User user GROUP BY user.age

通过该语句,我们获得了一系列的统计数据。对于Group by子句获得的结果集而言,我们可以通过Having子句进行筛选。例如,在上例中,我们对同龄用户进行了统计,获得了每个年龄层次中的用户数量,假设我们只对超过10人的年龄组感兴趣,可用以下语句实现:

SELECT COUNT(user),user.age FROM User user GROUP BY user.age HAVING COUNT(user) > 10

参数绑定

SQL注入

在解释参数绑定的使用时,我们先来解释一下什么是SQL注入。

SQL Injection是常见的系统攻击手短,这种攻击方式的目标是针对由SQL字符串拼接造成的漏洞。如,为了实现用户登录功能,我们编写了以下代码:

FROM User user WHERE user.name='"+username+"' AND user.password='"+password+"'

从逻辑上讲,该HQL并没有错误,我们根据用户名和密码从数据库中读取相应的记录,如果找到记录,则认为用户身份合法。

假设这里的变量username和password是来自于网页上输入框的数据。现在我们来做个尝试,在登录网页上输入用户名:"'Erica' or 'x'='x'",密码随意,也可以登录成功。

此时的HQL语句为:

FROM User user WHERE user.name='Erica' OR 'x'='x' AND user.password='fasfas'

此时,用户名中的OR 'x'='x'被添加到了HQL并作为子句执行,where逻辑为真,而密码是否正确就无关紧要。

这就是SQL Injection攻击的基本原理,而字符串拼接而成的HQL是安全漏洞的源头。参数的动态绑定机制可以妥善处理好以上问题。

Hibernate提供顺序占位符以及引用占位符,将分别举例说明:

顺序占位符:

String hql = "from User user WHERE user.name = ? AND user.age = ?";
List<User> list = session.createQuery(hql).setString(0, "Erica")
.setInteger(1, 10).list();

引用占位符:

String hql = "from User user WHERE user.uName = :name AND user.uAge = :age";
List<User> list = session.createQuery(hql).setString("name", "Erica")
.setInteger("age", 10).list();

我们甚至还可以用一个JavaBean来封装查询参数。

参数绑定机制可以使得查询语法与具体参数数值相互独立。这样,对于参数不同,查询语法相同的查询操作,数据库即可实施性能优化策略。同时,参数绑定机制也杜绝了参数值对查询语法本身的影响,这也就是避免了SQL Injection的可能。

引用查询

我们可能遇到过如下编码规范:“代码中不允许出现SQL语句”。

SQL语句混杂在代码之间将破坏代码的可读性,并似的系统的可维护性降低。为了避免这样的情况,我们通常采取将SQL配置化的方式,也就是说,将SQL保存在配置文件中。Hibernate提供了HQL可配置化的内置支持。

我们可以在实体映射文件中,通过query节点定义查询语句(与class节点同级):

<query name="queryTest"><![CDATA[FROM User user where user.uAge < 20]]></query>

需要注意的是,我们是将HQL语句写入到了xml文件中,所以可能会造成冲突。例如HQL语句的的“<”(小于)与xml的语法有冲突。所以我们会用CDATA将其包裹。

之后我们可以通过session的getNamedQuery方法从配置文件中调用对应的HQL,如:

Query query = session.getNamedQuery("queryTest");
List<User> list = query.list();
for(User user : list){
System.out.println(user);
}

关联查询

关于这部分的内容,参考了很多书上的资料,但都感觉讲的不够清晰,也就是说没有结合到实际的情况中去。下面将按照一个视频教程上的顺序来介绍关联查询。

关于这部分的知识点,是鉴于已经对关联连接查询有所了解的基础上,比如懂得什么是左外连接、内连接等。 下面就开始总结:

HQL迫切左外连接

LEFT JOIN FETCH关键字表示使用迫切左外连接策略。 首先看一下例子中的实体类,这是一个双向1-N的映射(关于1-N的映射在之前的博客中有介绍Hibernate关系映射2:双向1-N关联).

实体类:

public class Department {

    private Integer id;
private String name;
private Set<Employee> emps = new HashSet<>();
//省却get和set方法
}
public class Employee { private Integer id;
private String name;
private float salary;
private String email; private Department dept;
//省去get和set方法
}

迫切左外连接

String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for (Department dept : depts) {
System.out.println(dept.getName() + "-" + dept.getEmps().size());
}

上面的例子中是想得到所有的部门以及其中的员工。我们通过DISTINCT进行去重。 注意的点:

  • list()方法中返回的集合中存放实体对象的引用。每个Department对象关联的Employee集合都将被初始化,存放所有关联的Employee的实体对象
  • 查询结果中可能会包含重复元素,可以通过DISTINCT关键字去重,同样由于list中存放的实体对象的引用,所以可以通过HashSet来过滤重复对象。例如:
List<Department> depts = query.list();
depts = new ArrayList<>(new LinkedHashSet(depts));

左外连接

String hql = "FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Object[]> result = query.list();
System.out.println(result);
for (Object[] objs : result) {
System.out.println(Arrays.asList(objs));
}

注意的是:通过左外连接返回的list是一个包含Department和与之连接的Employee的object数组。所以我们还需要对数组进行处理,并且有重复。鉴于此,我们可以通过DISTINCT进行处理。

String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql); List<Department> depts = query.list();
System.out.println(depts.size()); for (Department dept : depts) {
System.out.println(dept.getName() + ", " + dept.getEmps().size());
}

注意:

  • list方法返回的集合中存放的是对象数组类型
  • 根据配置文件来决定Employee集合的检索策略,比如是fetch、lazy啊或者怎样,还不是像迫切左外连接那样。

我们真正进行的过程中,一般都会使用迫切左外连接。因为迫切左外连接只发送了一条SQL语句将所有信息都查出来,而左外连接就算不使用集合的对象,也会进行查询,而当真正使用集合的时候,又会再去查询。所以性能上迫切左外连接要好。

子查询

子查询可以在HQL中利用另外一条HQL的查询结果。 例如:

FROM User user WHERE (SELECT COUNT(*) FROM user.address) > 1

HQL中,子查询必须出现在where子句中,且必须以一对圆括号包围。

深入HQL学习以及HQL和SQL的区别的更多相关文章

  1. Hibernate学习之hql 与sql

    Hibernate中查询: createQuery( String qlString)使用的是HQL语句: createNativeQuery (String sqlString)使用的是SQL语句: ...

  2. Hive HQL学习

    HQL学习   1.hive的数据类型 2.hive_DDL 2.1创建.删除.修改.使用数据库     Default数据库,默认的,优先级相对于其他数据库是最高的   2.2重点:创建表_内部表_ ...

  3. Hibernate 检索查询的几种方式(HQL,QBC,本地SQL,集成Spring等)

    1.非集成Spring hibernate的检索方式,主要有以下五种. 1.导航对象图检索方式.(根据已经加载的对象,导航到其他对象.) 2.OID检索方式.(按照对象的OID来检索对象.) 3.HQ ...

  4. 用Hibernate框架把hql生成可执行的sql语句-Oracle方言

    引言:为什么这样的需求,源自公司项目需要,公司的项目使用java的struts2+spring2.5+oracle中间件tuxedo,数据库用的是Oracle,但由于不直接连接数据库,用中间件处理的方 ...

  5. HQL和SQL的区别

    1.hql与sql的区别 sql 面向数据库表查询 hql 面向对象查询 hql : from 后面跟的 类名+类对象 where 后 用 对象的属性做条件 sql: from 后面跟的是表名    ...

  6. MyBatis学习 之 二、SQL语句映射文件(2)增删改查、参数、缓存

    目录(?)[-] 二SQL语句映射文件2增删改查参数缓存 select insert updatedelete sql parameters 基本类型参数 Java实体类型参数 Map参数 多参数的实 ...

  7. MyBatis学习 之 二、SQL语句映射文件(1)resultMap

    目录(?)[-] 二SQL语句映射文件1resultMap resultMap idresult constructor association联合 使用select实现联合 使用resultMap实 ...

  8. ASP.NET MVC 5 学习教程:使用 SQL Server LocalDB

    原文 ASP.NET MVC 5 学习教程:使用 SQL Server LocalDB 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 ...

  9. SQL反模式学习笔记18 减少SQL查询数据,避免使用一条SQL语句解决复杂问题

    目标:减少SQL查询数据,避免使用一条SQL语句解决复杂问题 反模式:视图使用一步操作,单个SQL语句解决复杂问题 使用一个查询来获得所有结果的最常见后果就是产生了一个笛卡尔积.导致查询性能降低. 如 ...

随机推荐

  1. Swift 的 pod 第三方库

    #HTTPpod 'Alamofire' #Elegant HTTP Networking in Swiftpod 'SwiftHTTP' #Thin wrapper around NSURLSess ...

  2. 详解php的安装模式---CGI,FASTCGI,php-fpm,mod_php,mod_cgi,mod_fcgid

    1. CGI CGI是通用网关接口,HTTP服务器使用这样的接口程序来和“其他程序”(比如PHP的解释器程序)通讯,这个“其他程序”可以使用任何计算机语言来编写,它通过CGI这个接口从HTTP服务器取 ...

  3. EF操作VS中

    1.安装mysql server 2.安装MySql的VS插件(版本请下载最新版,百度自己搜索,这个不用多说)mysql-for-visualstudio-1.2.3.msi 3.安装用于.net连接 ...

  4. Linux Ubuntu 打开.exe文件

    这两天在编译Android源码,进行到要在Linux里安装烧录软件那一步,要先装驱动,故了解了如何在linux下打开.exe文件. .exe 文件在linux下不能直接打开,可有两种方式打开:. 1. ...

  5. Android 开发之Android 应用程序如何调用支付宝接口

    1.到支付宝官网,下载支付宝集成开发包 由于android设备一般用的都是无线支付,所以我们申请的就是支付宝无线快捷支付接口.下面是申请的地址以及下载接口开发包的网址:https://b.alipay ...

  6. C++的历史与现状

    在31年前(1979年),一名刚获得博士学位的研究员,为了开发一个软件项目发明了一门新编程语言,该研究员名为Bjarne Stroustrup,该门语言则命名为——C with classes,四年后 ...

  7. [shell]C语言调用shell脚本接口

    Use popen if you want to run a shell command and want the parent process to be able to talk to the c ...

  8. EasyUI Window和Layout

    我们建立tabs内容. <div class="easyui-window" title="Layout Window" icon="icon- ...

  9. Tagger: Deep Unsupervised Perceptual Grouping

    没看论文,但看了介绍视频,简直做了一个小型纪录片. 视频地址:https://www.youtube.com/watch?v=jfB_lWZA4Qo 中文翻译:http://blog.csdn.net ...

  10. javascript -- 事件捕获,事件冒泡

    使用js的时候,当给子元素和父元素定义了相同的事件,比如都定义了onclick事件,单击子元素时,父元素的onclick事件也会被触发.js里称这种事件连续发生的机制为事件冒泡或者事件捕获. 为什么会 ...