导读
HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:
- 在查询语句中设定各种查询条件;
- 支持投影查询, 即仅检索出对象的部分属性;
- 支持分页查询;
- 支持连接查询;
- 支持分组查询, 允许使用 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 查询语句中的主体是数据库表及表的字段。
一:属性查询(SimplePropertyQuery)
1,单一属性查询
- 返回结果集属性列表,元素类型和实体类中相应的属性一致
2, 多个属性查询,
- 返回集合中的元素是object数组,
- 其中数组元素的类型和对应属性在实体类中的类型一致
for(Iterator iterator=list.iterator();iterator.hasNext();)
{
Object[] obj=(Object[])iterator.next();
System.out.println("sid:="+obj[0].toString()+" sname:="+obj[1]);
}
List<Object[]> list = session.createQuery(
"select c.cid,c.cname from Customer c").list();
for (Object[] objects: list)
{
System.out.println(Arrays.toString(objects));
}
3, 通过hql来动态实例化对象
List list=session.createQuery("select new Student(sid,sname) from Student").list();
for (Iterator iterator=list.iterator();iterator.hasNext();){
Student student=(Student) iterator.next();
System.out.println("sid:="+student.getSid()+" sname:="+student.getSname());
}
4,采用别名查询
//List list=session.createQuery("select s.sid, s.sname from Student as s").list();
List list=session.createQuery("select s.sid, s.sname from Student s").list();
二:简单对象查询(SimplyObjectQuery)
1,返回对象集合(可以忽略select)
//List list=session.createQuery("from Student").list();
//List list=session.createQuery("from Student as s").list();
List list=session.createQuery("from Student s").list();
- 在HQL语句中,本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。
- 同时,在Hibernate中,查询的目标实体存在继承关系的判定,如果
from User
将返回所有User以及User子类的记录。 - 假设系统中存在User的两个子类:SysAdmin和SysOperator,那么该hql语句返回的记录将包含这两个子类的所有数据,即使SysAdmin和SysOperator分别对应了不同的库表。
2,采用select查询对象
- 必须要使用别名
//List list=session.createQuery("select s from Student as s").list();
List list=session.createQuery("select s from Student s").list();
3,查询单一对象的操作
// 不支持 select * from Customer写法.可以写成 select 别名 from Customer as 别名;
Object obj=session.createQuery("select s from Student s where s.sid=:mysid")
.setParameter("mysid", 12)
.uniqueResult();
4,分页查询
- setFirstResult(int firstResult)
- Set the first row to retrieve.
- If not set, rows will be retrieved beginnning from row 0.
- firstResult - a row number, numbered from 0
List list=session.createQuery("select s from Student s")
.setFirstResult(5)
.setMaxResults(5)
.list();
三:简单条件查询(SimpleConditionQuery)
1,可以拼凑字符串
List stuList=session.createQuery
("select s.sid, s.sname from Student as s where s.sname like '%1%'").list();
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提供顺序占位符以及引用占位符,将分别举例说明:
2,注意:可以采用 ?占位的方式来传递参数
- 参数的索引从0开始
- 传递的参数值不能用''号括起来
- 注意方法链编程
/*
*Query query=ession.createQuery("select s.sid, s.sname from Student as s where s.sname like ?");
*query.setParameter(0, "%1%");
*List stuList=query.list();
*/
List stuList=session.createQuery("select s.sid, s.sname from Student as s where s.sname like ?")
.setParameter(0, "%1%")
.list();
3,通过 参数名(:参数名)传参的方式进行查询
/*Object obj=session.createQuery("select s.sid, s.sname from Student as s where s.sname like :myname and s.sid=:myid")
.setParameter("myname", "%1%")
.setParameter("myid", 98)
.uniqueResult();//.list();
Object[] objs=(Object[])obj;
System.out.println("s.sid:="+objs[0].toString()+" s.sname:="+objs[1].toString());
*/
List stuList=session.createQuery("select s.sid, s.sname from Student as s where s.sname like :myname and s.sid=:myid")
.setParameter("myname", "%1%")
.setParameter("myid", 98)
.list();
4,传递多个参数的查询
- 支持in查询,需要setParameterList("myids", new Object[]{12,13,14})的方式进行参数传递
List stuList=session.createQuery("select s.sid, s.sname from Student as s where s.id in(:myids)")
.setParameterList("myids", new Object[]{12,13,14})
.list();
5, 查询2009年2月入学的学员信息
- 支持Sql函数查询
List stuList=session.createQuery("select s.sid, s.sname ,s.schoolDate from Student as s where
year(s.schoolDate)=:myyear and month(s.schoolDate)=:mymonth")
.setParameter("myyear", 2009)
.setParameter("mymonth", 2)
.list();
6,查询2009-02-10到2009-02-15号入学的学员信息
- 注意:在进行日期查询时
- setParameter("startDate", formatter.parse("2009-02-10 00:00:00"))
- 方法中第二个参数是一个Object对象(Date类型对象)
SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List stuList=session.createQuery("select s.sid, s.sname ,s.schoolDate
from Student as s where s.schoolDate between :startDate and :endDate")
.setParameter("startDate", formatter.parse("2009-02-10 00:00:00"))
.setParameter("endDate", formatter.parse("2009-02-15 23:59:59"))
.list();
7,绑定实体
List<Order> list = session
.createQuery("from Order o where o.customer = ?")
.setEntity(0, customer).list();
for (Order order : list) {
System.out.println(order);
}
四:原生SQL语句查询(SqlQueryTest_4)
- 这里用的是createSQLQuery().list();
List stuList=session.createSQLQuery("select * from Student").list();
五:外置命名查询(NamedQueryTest)
* 1.在映射文件中采用<query>标签来定义HQL语句,不必限定文件!!!
- 注意:映射文件中的HQL语句不可以出错,否则会导致 QuerySyntaxException,
- 特别是要查询的实体类名写错的情况。(Error in named query: selectAllStudents org.hibernate.hql.ast.QuerySyntaxException: Students is not mapped)。
如果我们将HQL代码写在类中,那么编译后我们非常难以维护,为了后期代码的可维护行,我们需要将HQL代码写在对应类的.hbm.xml文件中,例如:
<queryname="queryUserRanage">
FROM User WHERE id BETWEEN ? AND ?
</query>
在Java代码中
Query query=session.getNamedQuery("queryUserRanage");
List<User> list=query
.setParameter(0, 10)
.setParameter(1, 20)
.list();
for(User user:list){
System.out.println(user);
}
通常我们使用参数的方式,这样可以很直观知道每个参数的作用
<queryname="queryUserRanage">
FROM User WHERE id BETWEEN :minId AND :maxId
</query>
但是如果在SQL语句中存在>、<等xml中特殊字符,这些字符在xml中都有特殊的意义,所有我们不能直接这样写
【Error】
<queryname="queryUserRanage">
FROM User WHERE id > :minId AND id< :maxId
</query>
【Right】
<queryname="queryUserRanage">
FROM User WHERE id > :minId AND < :maxId
</query>
虽然上面的方式我们可以正确执行,但是代码显示不够友好,所有我们推荐使用下面的一种方式
<queryname="queryUserRanage">
<![CDATA[FROM User WHERE id > :minId AND < :maxId]]
</query>
CDATA代码块说明在代码中的语句不需要转义,我们一个HQL都推荐用CDATA块来包裹着
六:对象导航查询(ObjectNavQueryTest)
List list= session.createQuery("select s.sname from Student as s where s.clsInfo.cname like :myname")
.setParameter("myname", "%1%")
.list();
七:联合查询(JoinQueryTes)
1,联合查询* 内连接查询
- 获取已经参加班级报名的所有学生名号和其所在班级名称
List list=session.createQuery("select s.sname,c.cname from Student as s join s.clsInfo as c").list();
2,联合查询* 左连接查询
- 获取所有班级名称和其学员姓名
- 以班级信息表为主表,进行左外连接查询
List list=session.createQuery("select c.cname ,s.sname from ClassInfo as c left join c.studentSet as s").list();
3,联合查询 * 右连接查询
- 获取所有学员姓名和其所属班级名称
- 以学员信息表为主表,进行右外连接查询
List list=session.createQuery("select s.sname, c.cname from ClassInfo as c right join c.studentSet as s").list();
八:(StateQueryTest)
1,聚合函数使用
//List list=session.createQuery("select count(*) from Student").list();
//long count=(Long) list.get(0);
long count=(Long) session.createQuery("select count(*) from Student").uniqueResult();
2,分组查询
hql="SELECT age, COUNT(age) num FROM User WHERE age>10 GROUP BY age HAVING COUNT(age)>1
ORDER BY num DESC";
在HAVING中,不能使用别名num,但在ORDER BY中可以使用别名num
List list=session.createQuery("select c.cname, count(s) from Student as s inner join
s.clsInfo as c group by c.cname order by c.cname").list();
3,分页查询
Query query = session.createQuery("from Order");
query.setFirstResult(20);
query.setMaxResults(10);
List<Order> list = query.list();
for (Order order : list) {
System.out.println(order);
}
9.更新和删除
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子句的时候,必须特别注意它们对缓存策略的影响,极有可能导致缓存同步上的障碍。
10,关联查询
关于这部分的内容,参考了很多书上的资料,但都感觉讲的不够清晰,也就是说没有结合到实际的情况中去。下面将按照一个视频教程上的顺序来介绍关联查询。
关于这部分的知识点,是鉴于已经对关联连接查询有所了解的基础上,比如懂得什么是左外连接、内连接等。 下面就开始总结:
HQL迫切左外连接
LEFT JOIN FETCH
关键字表示使用迫切左外连接策略。 首先看一下例子中的实体类,这是一个双向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语句将所有信息都查出来,而左外连接就算不使用集合的对象,也会进行查询,而当真正使用集合的时候,又会再去查询。所以性能上迫切左外连接要好。
11,子查询
子查询可以在HQL中利用另外一条HQL的查询结果。 例如:
FROM User user WHERE (SELECT COUNT(*) FROM user.address) > 1
HQL中,子查询必须出现在where子句中,且必须以一对圆括号包围。
来源: http://carolli.iteye.com/blog/909542
来源: https://www.w3cschool.cn/hibernate_articles/gapw1ioo.html
来源: http://www.cnblogs.com/caoyc/p/5606444.html