深入HQL学习:HQL和SQL的区别
在查询语句中设定各种查询条件;
支持投影查询, 即仅检索出对象的部分属性;
支持分页查询;
支持连接查询;
支持分组查询, 允许使用 having 和 group by 关键字;
提供内置聚集函数, 如 sum(), min() 和 max();
支持子查询;
支持动态绑定参数;
能够调用 用户定义的 sql 函数或标准的 sql 函数。
hql 查询包括以下步骤:
获取hibernate session对象。编写hql语句
以hql语句作为参数,调用session的createquery方法创建查询对象。
如果hql语句包含参数,则调用query的setxxx方法为参数赋值。
调用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 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 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 list = session.createquery(hql).setstring("name", "erica") .setinteger("age", 10).list();
我们甚至还可以用一个javabean来封装查询参数。
参数绑定机制可以使得查询语法与具体参数数值相互独立。这样,对于参数不同,查询语法相同的查询操作,数据库即可实施性能优化策略。同时,参数绑定机制也杜绝了参数值对查询语法本身的影响,这也就是避免了sql injection的可能。
引用查询
我们可能遇到过如下编码规范:“代码中不允许出现sql语句”。
sql语句混杂在代码之间将破坏代码的可读性,并似的系统的可维护性降低。为了避免这样的情况,我们通常采取将sql配置化的方式,也就是说,将sql保存在配置文件中。hibernate提供了hql可配置化的内置支持。
我们可以在实体映射文件中,通过query节点定义查询语句(与class节点同级):
需要注意的是,我们是将hql语句写入到了xml文件中,所以可能会造成冲突。例如hql语句的的“<”(小于)与xml的语法有冲突。所以我们会用cdata将其包裹。
之后我们可以通过session的getnamedquery方法从配置文件中调用对应的hql,如:
query query = session.getnamedquery("querytest"); list 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 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 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 depts = query.list(); depts = new arraylist<>(new linkedhashset(depts));
左外连接
string hql = "from department d left join d.emps"; query query = session.createquery(hql); list 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 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子句中,且必须以一对圆括号包围。