Hibernate 提供了以下几种检索对象的方式:
HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似。在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式。
① 它有如下功能:
② HQL 检索方式包括以下步骤:
Qurey 接口支持方法链编程风格, 它的 setXxx() 方法返回自身实例, 而不是 void 类型。
③ HQL vs SQL:
HQL 查询语句是面向对象的, Hibernate 负责解析 HQL 查询语句, 然后根据对象-关系映射文件中的映射信息, 把 HQL 查询语句翻译成相应的 SQL 语句。 HQL 查询语句中的主体是域模型中的类及类的属性。
SQL 查询语句是与关系数据库绑定在一起的。SQL 查询语句中的主体是数据库表及表的字段。
④ 绑定参数
Hibernate 的参数绑定机制依赖于 JDBC API 中的 PreparedStatement 的预定义 SQL 语句功能。
HQL 的参数绑定由两种形式:
“:”
开头“?”
来定义参数位置相关方法:
HQL 采用 ORDER BY 关键字对查询结果排序。
这里以Department:Employee=1:N双向一对多为例。
Department类如下:
public class Department {
private Integer id;
private String name;
private Set emps = new HashSet<>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set getEmps() {
return emps;
}
public void setEmps(Set emps) {
this.emps = emps;
}
@Override
public String toString() {
return "Department [id=" + id + "]";
}
}
Department.hbm.xml如下:
Employee类如下:
public class Employee {
private Integer id;
private String name;
private float salary;
private String email;
private Department dept;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Employee [id=" + id + "]";
}
public Employee(String email, float salary, Department dept) {
super();
this.salary = salary;
this.email = email;
this.dept = dept;
}
public Employee() {
// TODO Auto-generated constructor stub
}
}
Employee.hbm.xml如下:
测试HQL查询代码如下:
@Test
public void testHQL(){
//1. 创建 Query 对象
//基于位置的参数.
String hql = "FROM Employee e WHERE e.salary > ? AND e.email LIKE ? AND e.dept = ? "
+ "ORDER BY e.salary";
Query query = session.createQuery(hql);
//2. 绑定参数--位置索引绑定方式
//Query 对象调用 setXxx 方法支持方法链的编程风格.
Department dept = new Department();
dept.setId(1);
query.setFloat(0, 3000)
.setParameter(1, "%A%",StringType.INSTANCE)
.setEntity(2, dept);
//3. 执行查询
List emps = query.list();
System.out.println(emps.size());
}
测试结果如下:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.NAME as NAME2_1_,
employee0_.SALARY as SALARY3_1_,
employee0_.EMAIL as EMAIL4_1_,
employee0_.DEPT_ID as DEPT_ID5_1_
from
GG_EMPLOYEE employee0_
where
employee0_.SALARY>?
and (
employee0_.EMAIL like ?
)
and employee0_.DEPT_ID=?
order by
employee0_.SALARY
1
使用命名参数进行参数绑定:
@Test
public void testHQLNamedParameter(){
//1. 创建 Query 对象
//基于命名参数.
String hql = "FROM Employee e WHERE e.salary > :sal AND e.email LIKE :email";
Query query = session.createQuery(hql);
//2. 绑定参数
query.setFloat("sal", 7000)
.setString("email", "%A%");
//3. 执行查询
List emps = query.list();
System.out.println(emps.size());
}
⑤ 分页查询
setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult 表示这个对象在查询结果中的索引位置, 索引位置的起始值为 0。默认情况下, Query 从查询结果中的第一个对象开始检索。
setMaxResults(int maxResults): 设定一次最多检索出的对象的数目。在默认情况下, Query 和 Criteria 接口检索出查询结果中所有的对象。
测试代码如下:
@Test
public void testPageQuery(){
String hql = "FROM Employee";
Query query = session.createQuery(hql);
int pageNo = 1;
int pageSize = 5;
List emps = query.setFirstResult((pageNo - 1) * pageSize)
.setMaxResults(pageSize)
.list();
System.out.println(emps);
测试结果如下:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.NAME as NAME2_1_,
employee0_.SALARY as SALARY3_1_,
employee0_.EMAIL as EMAIL4_1_,
employee0_.DEPT_ID as DEPT_ID5_1_
from
GG_EMPLOYEE employee0_ limit ?
[Employee [id=1], Employee [id=2], Employee [id=3], Employee [id=4], Employee [id=5]]
⑥ 在映射文件中定义命名查询语句
Hibernate 允许在映射文件中定义字符串形式的查询语句。
元素用于定义一个 HQL 查询语句, 它和
元素并列。
在Employee.hbm.xml中添加query节点如下:
:minSal AND e.salary < :maxSal]]>
在程序中通过 Session 的 getNamedQuery() 方法获取查询语句对应的 Query 对象:
@Test
public void testNamedQuery(){
Query query = session.getNamedQuery("salaryEmps");
List emps = query.setFloat("minSal", 5000)
.setFloat("maxSal", 10000)
.list();
System.out.println(emps.size());
}
测试结果如下:
Hibernate:
select
employee0_.ID as ID1_1_,
employee0_.NAME as NAME2_1_,
employee0_.SALARY as SALARY3_1_,
employee0_.EMAIL as EMAIL4_1_,
employee0_.DEPT_ID as DEPT_ID5_1_
from
GG_EMPLOYEE employee0_
where
employee0_.SALARY>?
and employee0_.SALARY
7
⑦ 投影查询
投影查询,查询结果仅包含实体的部分属性, 通过 SELECT 关键字实现。
Query 的 list() 方法返回的集合中包含的是数组类型的元素, 每个对象数组代表查询结果的一条记录。
测试代码一如下:
@Test
public void testFieldQuery(){
String hql = "SELECT e.email, e.salary, e.dept FROM Employee e WHERE e.dept = :dept";
Query query = session.createQuery(hql);
Department dept = new Department();
dept.setId(1);
List
测试一结果如下:
Hibernate:
select
employee0_.EMAIL as col_0_0_,
employee0_.SALARY as col_1_0_,
employee0_.DEPT_ID as col_2_0_,
department1_.ID as ID1_0_,
department1_.NAME as NAME2_0_
from
GG_EMPLOYEE employee0_
inner join
GG_DEPARTMENT department1_
on employee0_.DEPT_ID=department1_.ID
where
employee0_.DEPT_ID=?
[[email protected], 5000.0, Department [id=1]]
[[email protected], 6000.0, Department [id=1]]
可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录, 使程序代码能完全运用面向对象的语义来访问查询结果集。
测试代码二如下:
@Test
public void testFieldQuery2(){
//实体类Employee需要有该构造器
String hql = "SELECT new Employee(e.email, e.salary, e.dept) "
+ "FROM Employee e "
+ "WHERE e.dept = :dept";
Query query = session.createQuery(hql);
Department dept = new Department();
dept.setId(1);
List result = query.setEntity("dept", dept).list();
for(Employee emp: result){
System.out.println(emp.getId() + ", " + emp.getEmail()
+ ", " + emp.getSalary() + ", " + emp.getDept());
}
}
测试二结果如下:
Hibernate:
select
employee0_.EMAIL as col_0_0_,
employee0_.SALARY as col_1_0_,
employee0_.DEPT_ID as col_2_0_
from
GG_EMPLOYEE employee0_
inner join //内连接
GG_DEPARTMENT department1_
on employee0_.DEPT_ID=department1_.ID
where
employee0_.DEPT_ID=?
Hibernate:
select
department0_.ID as ID1_0_0_,
department0_.NAME as NAME2_0_0_
from
GG_DEPARTMENT department0_
where
department0_.ID=?
null, [email protected], 5000.0, Department [id=1]
null, [email protected], 6000.0, Department [id=1]
另外,可以通过 DISTINCT 关键字来保证查询结果不会返回重复元素。
⑧ 报表查询
报表查询用于对数据分组和统计, 与 SQL 一样, HQL 利用 GROUP BY 关键字对数据分组, 用 HAVING 关键字对分组数据设定约束条件,过滤分组数据。
在 HQL 查询语句中可以调用以下聚集函数:
count()
min()
max()
sum()
avg()
测试代码如下:
@Test
public void testGroupBy(){
String hql = "SELECT min(e.salary), max(e.salary) "
+ "FROM Employee e "
+ "GROUP BY e.dept "
+ "HAVING min(salary) > :minSal";
Query query = session.createQuery(hql)
.setFloat("minSal", 5000);
List
测试结果如下:
Hibernate:
select
min(employee0_.SALARY) as col_0_0_,
max(employee0_.SALARY) as col_1_0_
from
GG_EMPLOYEE employee0_
group by
employee0_.DEPT_ID
having
min(employee0_.SALARY)>?
[7000.0, 8000.0]
[9000.0, 10000.0]
[7000.0, 8000.0]
⑨ 左外连接和迫切左外连接
左外连接:
使用select关键字测试代码如下:
@Test
public void testLeftJoinFetch(){
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List depts = query.list();
depts = new ArrayList<>(new LinkedHashSet(depts));
System.out.println(depts.size());
for(Department dept: depts){
System.out.println(dept.getName() + "-" + dept.getEmps().size());
}
}
测试结果如下:
Hibernate:
select
distinct department0_.ID as ID1_0_,
department0_.NAME as NAME2_0_
from
GG_DEPARTMENT department0_
left outer join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
14
Hibernate:
select
emps0_.DEPT_ID as DEPT_ID5_1_0_,
emps0_.ID as ID1_1_0_,
emps0_.ID as ID1_1_1_,
emps0_.NAME as NAME2_1_1_,
emps0_.SALARY as SALARY3_1_1_,
emps0_.EMAIL as EMAIL4_1_1_,
emps0_.DEPT_ID as DEPT_ID5_1_1_
from
GG_EMPLOYEE emps0_
where
emps0_.DEPT_ID=?
AA-2 //使用的时候再发送查询语句,SQL语句数目比较多
Hibernate:
select
emps0_.DEPT_ID as DEPT_ID5_1_0_,
emps0_.ID as ID1_1_0_,
emps0_.ID as ID1_1_1_,
emps0_.NAME as NAME2_1_1_,
emps0_.SALARY as SALARY3_1_1_,
emps0_.EMAIL as EMAIL4_1_1_,
emps0_.DEPT_ID as DEPT_ID5_1_1_
from
GG_EMPLOYEE emps0_
where
emps0_.DEPT_ID=?
BB-2
//...
不适用select关键字测试如下:
@Test
public void testLeftJoin(){
String hql = " FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List
测试结果如下:
Hibernate:
select
department0_.ID as ID1_0_0_,
emps1_.ID as ID1_1_1_,
department0_.NAME as NAME2_0_0_,
emps1_.NAME as NAME2_1_1_,
emps1_.SALARY as SALARY3_1_1_,
emps1_.EMAIL as EMAIL4_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_1_
from
GG_DEPARTMENT department0_
left outer join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
[[Ljava.lang.Object;@1763992e, [Ljava.lang.Object;@5c92166b, [Ljava.lang.Object;@659925f4, [Ljava.lang.Object;@4cd1c1dc, [Ljava.lang.Object;@47f08b81, [Ljava.lang.Object;@b9dfc5a, [Ljava.lang.Object;@2787de58, [Ljava.lang.Object;@659a2455, [Ljava.lang.Object;@267517e4, [Ljava.lang.Object;@426e505c, [Ljava.lang.Object;@5b022357, [Ljava.lang.Object;@6f8e0cee, [Ljava.lang.Object;@614aeccc, [Ljava.lang.Object;@5116ac09, [Ljava.lang.Object;@1bc425e7, [Ljava.lang.Object;@4b2a30d, [Ljava.lang.Object;@322803db, [Ljava.lang.Object;@56ba8773, [Ljava.lang.Object;@6ceb7b5e]
[Department [id=1], Employee [id=1]]Employee [id=1]
[Department [id=1], Employee [id=2]]Employee [id=2]
[Department [id=2], Employee [id=3]]Employee [id=3]
[Department [id=2], Employee [id=4]]Employee [id=4]
[Department [id=3], Employee [id=5]]Employee [id=5]
[Department [id=3], Employee [id=6]]Employee [id=6]
[Department [id=4], Employee [id=7]]Employee [id=7]
[Department [id=4], Employee [id=8]]Employee [id=8]
[Department [id=5], Employee [id=9]]Employee [id=9]
[Department [id=5], Employee [id=10]]Employee [id=10]
会将Department和Employee一并查询出来,但是返回的是list
迫切左外连接:
list
), 每个 Department 对象关联的 Employee 集合都被初始化, 存放所有关联的 Employee 的实体对象.使用select关键字测试代码如下:
@Test
public void testLeftJoinFetch(){
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List depts = query.list();
depts = new ArrayList<>(new LinkedHashSet(depts));
System.out.println(depts.size());
for(Department dept: depts){
System.out.println(dept.getName() + "-" + dept.getEmps().size());
}
}
测试结果如下:
Hibernate:
select
distinct department0_.ID as ID1_0_0_,
emps1_.ID as ID1_1_1_,
department0_.NAME as NAME2_0_0_,
emps1_.NAME as NAME2_1_1_,
emps1_.SALARY as SALARY3_1_1_,
emps1_.EMAIL as EMAIL4_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_0__,
emps1_.ID as ID1_1_0__
from
GG_DEPARTMENT department0_
left outer join //使用左外连接一并查出相关联的实体对象
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
14
AA-2
BB-2
CC-2
DD-2
EE-2
FF-0
GG-0
HH-0
II-0
JJ-0
KK-0
LL-0
MM-0
NN-0
不使用select关键字测试如下:
@Test
public void testLeftJoin(){
String hql = " 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());
}
测试结果如下:
Hibernate:
select
department0_.ID as ID1_0_0_,
emps1_.ID as ID1_1_1_,
department0_.NAME as NAME2_0_0_,
emps1_.NAME as NAME2_1_1_,
emps1_.SALARY as SALARY3_1_1_,
emps1_.EMAIL as EMAIL4_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_0__,
emps1_.ID as ID1_1_0__
from
GG_DEPARTMENT department0_
left outer join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
19
AA, 2
AA, 2
BB, 2
BB, 2
CC, 2
CC, 2
DD, 2
DD, 2
EE, 2
EE, 2
FF, 0
GG, 0
HH, 0
II, 0
JJ, 0
KK, 0
LL, 0
MM, 0
NN, 0
会将Department和Employee一并查询出来,但是返回的是list
类型。
⑩ HQL (迫切)内连接
内连接:
测试代码如下:
@Test
public void testInnerJoin(){
String hql = "FROM Department d INNER JOIN d.emps";
// String hql = "FROM Department d INNER JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List
测试结果如下:
Hibernate:
select
department0_.ID as ID1_0_0_,
emps1_.ID as ID1_1_1_,
department0_.NAME as NAME2_0_0_,
emps1_.NAME as NAME2_1_1_,
emps1_.SALARY as SALARY3_1_1_,
emps1_.EMAIL as EMAIL4_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_1_
from
GG_DEPARTMENT department0_
inner join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
[[Ljava.lang.Object;@57fae983, [Ljava.lang.Object;@4a29f290, [Ljava.lang.Object;@4bee18dc, [Ljava.lang.Object;@44f3fe83, [Ljava.lang.Object;@44c5a16f, [Ljava.lang.Object;@417d6615, [Ljava.lang.Object;@7a6ebe1e, [Ljava.lang.Object;@21325036, [Ljava.lang.Object;@489543a6, [Ljava.lang.Object;@6272c96f]
[Department [id=1], Employee [id=1]]
[Department [id=1], Employee [id=2]]
[Department [id=2], Employee [id=3]]
[Department [id=2], Employee [id=4]]
[Department [id=3], Employee [id=5]]
[Department [id=3], Employee [id=6]]
[Department [id=4], Employee [id=7]]
[Department [id=4], Employee [id=8]]
[Department [id=5], Employee [id=9]]
[Department [id=5], Employee [id=10]]
迫切内连接:
测试代码如下:
@Test
public void testInnerJoin(){
String hql = "FROM Department d INNER 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());
}
}
测试结果如下:
Hibernate:
select
department0_.ID as ID1_0_0_,
emps1_.ID as ID1_1_1_,
department0_.NAME as NAME2_0_0_,
emps1_.NAME as NAME2_1_1_,
emps1_.SALARY as SALARY3_1_1_,
emps1_.EMAIL as EMAIL4_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_0__,
emps1_.ID as ID1_1_0__
from
GG_DEPARTMENT department0_
inner join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
10
AA, 2
AA, 2
BB, 2
BB, 2
CC, 2
CC, 2
DD, 2
DD, 2
EE, 2
EE, 2
对比可以发现,inner join和inner join fetch发送的SQL查询一样,差别在返回的接收对象类型。前者为list
。
另外,这里均使用的为“From”语句,并未使用select关键字。如果使用了select关键字,查出来的结果为list
,但是SQL语句也会有变化。
测试一代码如下:
@Test
public void testInnerJoin(){
String hql = "select d FROM Department d INNER 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());
}
}
测试结果如下:
Hibernate:
select
department0_.ID as ID1_0_,
department0_.NAME as NAME2_0_
from
GG_DEPARTMENT department0_
inner join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
10
Hibernate: // 使用的时候再次发送SQL语句进行查询
select
emps0_.DEPT_ID as DEPT_ID5_1_0_,
emps0_.ID as ID1_1_0_,
emps0_.ID as ID1_1_1_,
emps0_.NAME as NAME2_1_1_,
emps0_.SALARY as SALARY3_1_1_,
emps0_.EMAIL as EMAIL4_1_1_,
emps0_.DEPT_ID as DEPT_ID5_1_1_
from
GG_EMPLOYEE emps0_
where
emps0_.DEPT_ID=?
AA, 2
AA, 2
//...
而inner join fetch则无论是否使用select关键字,都会将关联的实体一并查询出来。
测试二代码如下:
@Test
public void testInnerJoin(){
String hql = "select d FROM Department d INNER 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());
}
}
测试二结果如下:
Hibernate:
select
department0_.ID as ID1_0_0_,
emps1_.ID as ID1_1_1_,
department0_.NAME as NAME2_0_0_,
emps1_.NAME as NAME2_1_1_,
emps1_.SALARY as SALARY3_1_1_,
emps1_.EMAIL as EMAIL4_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_1_,
emps1_.DEPT_ID as DEPT_ID5_1_0__,
emps1_.ID as ID1_1_0__
from
GG_DEPARTMENT department0_
inner join
GG_EMPLOYEE emps1_
on department0_.ID=emps1_.DEPT_ID
10
AA, 2
AA, 2
BB, 2
BB, 2
CC, 2
CC, 2
DD, 2
DD, 2
EE, 2
EE, 2
(11)关联级别运行时的检索策略
如果在 HQL 中没有显式指定检索策略, 将使用映射文件配置的检索策略。
HQL 会忽略映射文件中设置的迫切左外连接检索策略, 如果希望 HQL 采用迫切左外连接策略, 就必须在 HQL 查询语句中显式的指定它。
若在 HQL 代码中显式指定了检索策略, 就会覆盖映射文件中配置的检索策略。
具体参考博文:Hibernate检索策略详解