SSH Chapter 05 HQL 实用技术 笔记
本章任务:
- 使用HQL完成对数据表的查询
- 完成数据表的分页查询
技术内容:
前面我们学习了Hibernate的基础知识,掌握了如何使用Hibernate完成增,删,改及加载对象数据的方法 . 本章将学习如何使用Hibernate进行查询操作.
前面我们学习了Hibernate
的基础知识,掌握了如何使用Hibernate
完成增
,删
,改
及加载对象数据的方法 . 本章将学习如何使用Hibernate
进行查询操作.
1. 使用HQL语句
Hibernate总的来说共有三种查询方式:HQL、Criteria和SQL三种。
- HQL(Hibernate Query Language)是面向对象的查询语言,这一点也和Java语言的特性比较契合。它和SQL查询语言有些相像,但它使用的是类、对象和属性的概念,而没有表和字段的概念.
- Criteria查询也叫做 QBC查询 query by criteria,Criteria 是 Hibernate 提出的纯粹的面向对象的查询方式,在使用 Criteria 查询数据时,不需要书写任何一个SQL语句或者HQL语句,即时是一个完全不懂SQL的人,也完全可以使用 Criteria 所提供的各种API来设置查询条件、分页、排序、联合查询等等,也依然可以使用 Criteria 查询出想要的数据.
- SQL查询就是直接执行
SQL
语句的查询,可以在SQL
中利用不同数据库所特有的一些特性进行查询,例如:如果数库是Oracle,可以使用Oracle的关键字或函数等.
在Hibernate提供的各种检索方式中,HQL是官方推荐的查询语言,也是使用最广泛的一种检索方式,具体功能有很多,能满足实际开发的各种查询要求.HQL语句形式和SQL语句相似,但是不要被语法结构上的相似所迷惑,HQL是完全面向对象的,它可以理解继承,多态和关联之类的概念.
1.1 编写HQL语句
因为它写起来灵活直观,而且与所熟悉的SQL的语法差不太多。条件查询、分页查询、连接查询、嵌套查询,写起来与SQL语法基本一致,唯一不同的就是把表名换成了类或者对象。其它的,包括一些查询函数(count(),sum()等)、查询条件的设定等,全都跟SQL语法一样。
HQL语句中除了Java类和属性的名称外,查询语句对大小写不敏感,所以
SELECT
和select
是相同的,但是cn.hibernatedemo.entity.Dept
不等价于cn.hibernatedemo.entity.DEPT
.
HQL语句中的关键字建议使用小写字母. 下面以部门和员工为例,学习常用的HQL查询语法.
1. from子句
Hibernate
中最简单的HQL语句,形式如下:这几条HQL语句都能用于查询所有的部门.
-
from cn.hibernatedemo.entity.Dept
说明:cn.hibernatedemo.entity.Dept是完全限定类名
-
from Dept
说明:类名Dept省略了包名
-
from Dept as dept
-
from Dept dept
说明:这两条HQL语句为持久化类Dept指派了别名dept,可以在HQL语句中使用这个别名,关键字as是可选的.
2. select子句
select子句用于选取对象和属性.
-
select dept.deptName from Dept as dept
说明:select子句选取一个属性deptName,也可以选取多个属性.
-
select dept from Dept as dept
说明:select后跟的是别名dept
3. where子句
where字句允许你将返回的实例列表的范围缩小,用于表达查询的限制条件
-
from Dept where deptName = ‘SALES’
说明:这条HQL语句用于查询名称是SALES的部门.在where子句中直接使用属性名deptName.
-
from Dept as dept where dept.deptName = ‘SALES’
说明:这条HQL语句用于查询名称是SALES的部门,这条HQL语句指派了别名,在where子句中使用了完整的属性名dept.deptName.
-
from Dept dept where dept.location is not null
说明:这条HQL语句用于查询地址不为null的部门.
4. 使用表达式
表达式一般用字where子句中.以下两个HQL语句在where子句中分别使用了lower()函数和year()函数.
-
from Dept dept where lower(dept.deptName) = ‘sales’
说明:这条HQL语句用于查询名称是SALES的部门,不区分大小写.lower()函数用于把字符串的每个字母转换为小写.
-
from Emp where year(hireDate) = 1980
说明:这条HQL语句用于查询1980年入职的员工.year()函数用于获取日期字段的年份.
5. order by 子句
order by 子句用于按指定的属性排序
-
from Emp order by hireDate asc
说明:这条HQL语句用于查询所有的员工,并按员工入职时间升序排序,关键字asc或desc是可选的,用于按照升序或者降序进行排序,默认按升序排列.
-
from Emp order by hireDate,salary desc
说明:这条HQL语句用于查询所有的员工,并按员工入职时间升序,然后时间相同的再按员工工资降序排序.
1.2 执行HQL语句
HQL 语句准备好以后,执行HQL语句需使用如下代码构建Query
对象.
//定义HQL语句
String hql = "from Emp";
//构建Query对象
Query query = session.createQuery(hql);
Query
对象构建好以后,有两种方式执行查询语句并获取查询结果,一种是Query
对象的list()
方法,另一种是Query
对象的iterate()
方法.
1. 使用list()方法执行查询并输出结果:
示例1:
Emp.java
中的关键代码如下:
/**
* 雇员信息
*/
public class Emp implements Serializable {
private Integer empNo;
private String empName;
private String job;
private Date hiredate;
private Double salary;
//省略getter setter toString
}
Emp.hbm.xml
内容如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.houserent.entity">
<class name="Emp" table="`EMP`">
<id name="empNo" column="`EMPNO`">
<generator class="increment">
</generator>
</id>
<property name="empName" type="string" column="`ENAME`"/>
<property name="job" />
<property name="hiredate" />
<property name="salary" column="`SAL`"/>
</class>
</hibernate-mapping>
EmpDao.java
中的关键代码如下:
/**
* 雇员信息数据访问层
*/
public class EmpDao extends BaseDao {
public List<Emp> findAll(){
String hql = "from Emp;
Query<Emp> query = currentSession().createQuery(hql, Emp.class);
return query.list();
}
}
业务层中关键代码如下:
/**
* 雇员类业务层
*/
public class EmpBiz {
private EmpDao empDao = new EmpDao();
public List<Emp> findAllEmps(){
Transaction transaction = null;
List<Emp> list = null;
try {
transaction = empDao.currentSession().beginTransaction();
list = empDao.findAll();
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
}
测试方法中的关键代码如下:
@Test
public void testFindAllEmps(){
EmpBiz empBiz = new EmpBiz();
List<Emp> list = empBiz.findAllEmps();
list.forEach(System.out::println);
}
示例1执行后,控制台生成的SQL语句如下:
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_,
emp0_.ENAME as ENAME2_1_,
emp0_.JOB as JOB3_1_,
emp0_.SAL as SAL4_1_,
emp0_.HiREDATE as HiREDATE5_1_
from
EMP emp0_
where
emp0_.ENAME like ?
2. 使用iterate()方法执行查询并输出结果:
示例2:
EmpDao.java
中关键代码如下:
public Iterator<Emp> findIterator() {
//String hql = "from Emp where empName like '%"+"A"+"%'";
String hql = "from Emp";
Query<Emp> query = currentSession().createQuery(hql, Emp.class);
return query.iterate();
}
业务层代码如下:
public Iterator<Emp> findIterator() {
Transaction transaction = null;
Iterator<Emp> iterator = null;
try {
transaction = empDao.currentSession().beginTransaction();
iterator = empDao.findIterator();
Emp emp = null;
while (iterator.hasNext()){
emp = iterator.next();
System.out.println(emp);
}
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return iterator;
}
测试方法中的关键代码如下:
@Test
public void testFindIterator(){
EmpBiz empBiz = new EmpBiz();
Iterator<Emp> iterators = empBiz.findIterator();
}
示例2执行后,控制打印的SQL
语句如下:
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_0_,
emp0_.ENAME as ENAME2_1_0_,
emp0_.JOB as JOB3_1_0_,
emp0_.SAL as SAL4_1_0_,
emp0_.HiREDATE as HiREDATE5_1_0_
from
EMP emp0_
where
emp0_.EMPNO=?
Emp{empNo=7876, empName='ADAMS', job='CLERK', salary=1100.0, hireDate=0087-07-13 00:00:00.0}
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_0_,
emp0_.ENAME as ENAME2_1_0_,
emp0_.JOB as JOB3_1_0_,
emp0_.SAL as SAL4_1_0_,
emp0_.HiREDATE as HiREDATE5_1_0_
from
EMP emp0_
where
emp0_.EMPNO=?
Emp{empNo=7900, empName='JAMES', job='CLERK', salary=950.0, hireDate=1981-12-03 00:00:00.0}
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_0_,
emp0_.ENAME as ENAME2_1_0_,
emp0_.JOB as JOB3_1_0_,
emp0_.SAL as SAL4_1_0_,
emp0_.HiREDATE as HiREDATE5_1_0_
from
EMP emp0_
where
emp0_.EMPNO=?
Emp{empNo=7902, empName='FORD', job='ANALYST', salary=3000.0, hireDate=1981-12-03 00:00:00.0}
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_0_,
emp0_.ENAME as ENAME2_1_0_,
emp0_.JOB as JOB3_1_0_,
emp0_.SAL as SAL4_1_0_,
emp0_.HiREDATE as HiREDATE5_1_0_
from
EMP emp0_
where
emp0_.EMPNO=?
从Hibernate
生成的SQL语句中可以看出,Hibernate-list()与iterate()方法的区别.
- 对于list方法而言,实际上Hibernate是通过一条Select SQL获取所有的记录。并将其读出,填入到POJO中返回。
- 而iterate 方法,则是首先通过一条Select SQL 获取所有符合查询条件的记录的id,再对这个id 集合进行循环操作,通过单独的Select SQL 取出每个id 所对应的记录,之后填入POJO中返回。
- 也就是说,对于list 操作,需要一条SQL 完成。而对于iterate 操作,需要n+1条SQL。
看上去iterate方法似乎有些多余,但在不同的情况下确依然有其独特的功效,如对海量数据的查询,如果用list方法将结果集一次取出,内存的开销可能无法承受。
另一方面,对于我们现在的Cache机制而言,list方法将不会从Cache中读取数据,它总是一次性从数据库中直接读出所有符合条件的记录。而iterate 方法因为每次根据id获取数据,这样的实现机制也就为从Cache读取数据提供了可能,hibernate首先会根据这个id 在本地Cache 内寻找对应的数据,如果没找到,再去数据库中检索。
Query的两个方法,list() 和 iterate() , 两个方法都是把结果集列出来, 他们有4点不一样:
-
返回的类型不一样,list()返回List, iterate()返回Iterator
-
获取数据的方式不一样,list()会直接查数据库, iterate()会先到数据库中把id都取出来,然后真正要遍历某个对象的时候先到缓存中找.
如果找不到,以id为条件再发一条sql到数据库,这样如果缓存中没有数据,则查询数据库的次数为n+1。 -
iterate会查询2级缓存,list只会查询一级缓存。
-
list()中返回的List中每个对象都是原本的对象, iterate()中返回的对象是代理对象.(debug可以发现)
3 . 执行HQL语句的步骤:
(1) 获取Session
对象.
(2) 编写HQL
语句
(3) 创建Query
对象
(4) 执行查询,得到查询结果.
2. 在HQL语句中绑定参数
2.1 参数绑定形式
HQL参数绑定有以下两种形式:
1. 按参数位置绑定
在HQL语句中用"?"
占位符定义参数位置,形式如下:
String hql = "from Emp where job=? and salary=?";
以上HQL语句中定义了两个参数,可以通过setXXX()方法来绑定参数,注意第一个参数位置的下标为零:
query.setString(0,job);//为占位符赋值,下标从0开始
query.setDouble(1,salary);
Query
对象提供了绑定各种类型参数的方法,如果参数为字符串类型,可以调用setString
方法,如果参数为整数类型,可调用setInteger()
方法,依次类推.这些setXXX()
方法的第一个参数代表HQL
语句中参数的位置下标,第2个参数代表HQL
语句中参数的值.
按名称查找部门,使用
"?"
占位符来实现,示例3所示:
示例3:
DeptDao.java
中关键代码如下:
public List<Dept> findByDeptName(String deptName){
String hql = "from Dept as d where d.deptName=?";
Query<Dept> query = currentSession().createQuery(hql, Dept.class);
query.setString(0,deptName);//为占位符赋值,下标从0开始
return query.list();
}
业务层关键代码如下:
public List<Dept> findDeptByName(String deptName) {
Transaction transaction = null;
List<Dept> list = null;
try {
transaction = deptDao.currentSession().beginTransaction();
list = deptDao.findByDeptName(deptName);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法的关键代码如下:
@Test
public void testFindDeptByName(){
DeptBiz deptBiz = new DeptBiz();
List<Dept> list = deptBiz.findDeptByName("SALES");
list.forEach(System.out::println);
}
执行示例3,若Hibernate
使用的版本是5.X以前的,则控制台正常输出.
但是,若使用的Hibernate是5.X以后的版本,控制台会抛出如下异常:
java.lang.IllegalArgumentException: org.hibernate.QueryException: Legacy-style query parameters (`?`) are no longer supported; use JPA-style ordinal parameters (e.g., `?1`) instead : from cn.hibernatedemo.entity.Dept as d where d.deptName=? [from cn.hibernatedemo.entity.Dept as d where d.deptName=?]
分析异常原因是因为:Hibernate占位符?号已作废,请使用命名参数或者Jpa样式的占位参数代替
解决方案如下:
将Hibernate占位符?, 改成JPA占位符的方式(?号后面有带数字):
修改DeptDao.java
中的HQL语句,代码如下:
public List<Dept> findByDeptName(String deptName){
String hql = "from Dept as d where d.deptName=?0";
Query<Dept> query = currentSession().createQuery(hql, Dept.class);
query.setString(0,deptName);//为占位符赋值,下标从0开始
return query.list();
}
备注:其中"?"
后面的"0"
代表索引位置,在HQL语句中可重复出现,并不一定要从0开始,可以是任何数字,只是参数要与其对应上即可。
重新运行测试方法,控制台正常输出结果
值的注意的是:当我们在使用使用setXXX()
方法时,发现此类方法已过时,需要代替为setParameter()
,修改DeptDao.java
代码如下:
public List<Dept> findByDeptName(String deptName){
String hql = "from Dept as d where d.deptName=?0";
Query<Dept> query = currentSession().createQuery(hql, Dept.class);
//query.setString(0,deptName);//为占位符赋值,下标从0开始
query.setParameter(0,deptName);//为占位符赋值,下标与HQL语句中的?后面的数字对应
return query.list();
}
2. 按参数名称绑定
在HQL语句中可以定义命名参数,命名参数以":"
开头,形式如下:
String hql = "from Emp where job= :empJob and salary > :empSalary";
以上HQL语句定义了两个命名参数"empJob"
和"empSalary"
.接下来调用Query
对象的setXXX()
方法来绑定参数:
query.setString("empJob",empJob);
query.setDouble("empSalary",empSalary);
如上面的方法过时,可以使用以下方法代替:
query.setParameter("empJob",empJob);
query.setParameter("empSalary",empSalary);
这些方法中的第一个参数代表命名参数的名称,第二个参数代码命名参数的值.
使用命名参数来实现按名称查找部门,如示例4所示:
示例4:
DeptDao.java
中的关键代码如下:
public List<Dept> findByDeptName(String deptName) {
String hql = "from Dept as d where d.deptName=:name";
Query<Dept> query = currentSession().createQuery(hql, Dept.class);
query.setParameter("name", deptName);//为命名参数赋值
return query.list();
}
业务层和测试方法的关键代码与示例3相同
按名称绑定与按位置绑定相比有以下优势:
(1) 使用程序代码有较好的可读性.
(2) 按名称绑定的形式有利于程序代码的维护.对于按位置绑定的形式,如果参数在HQL语句中位置改变了,就必须修改相关绑定参数的代码,这就削弱了程序代码的健壮性和可维护性.
2.2 绑定各种类型的参数
1. Query接口提供的绑定不同的数据类型参数的方法:
- setBoolean() : 绑定类型为
java.lang.Boolean
的参数 - setByte() : 绑定类型为
java.lang.Byte
的参数 - setDouble() : 绑定类型为
java.lang.Double
的参数 - setDate() : 绑定类型为
java.util.Date
的参数 - setString() : 绑定类型为
java.lang.String
的参数
以上每个方法都有两种重载形式,如setString()方法:
(1) setString(int position,String val) : 按位置绑定参数
(2) setString(String name,String val) : 按名称绑定参数
以上用于绑定特定类型参数的方法,在Hibernate 5.X
中都已经废弃,不建议使用.
对此
Hibernate
还提供了setParameter()
方法,用于绑定任意类型的参数.该方法有多种重载形式,可灵活配置各种参数.用法如示例5所示:
示例5:
需求说明:查询职位是
"CLERK"
,工资大于1000元的职工有哪些?
EmpDao.java
关键代码如下:
public List<Emp> findByConditions(Object[] conditions){
String hql = "from Emp where job=?0 and salary>?1";
Query<Emp> query = currentSession().createQuery(hql, Emp.class);
if(conditions != null && conditions.length > 0){
for (int i = 0; i < conditions.length; i++) {
query.setParameter(i,conditions[i]);
}
}
return query.list();
}
EmpBiz.java
关键代码如下:
public List<Emp> findEmpsByConditions(Object[] conditions) {
Transaction transaction = null;
List<Emp> list = null;
try {
transaction = empDao.currentSession().beginTransaction();
list = empDao.findByConditions(conditions);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法关键代码如下:
@Test
public void testFindEmpByConditions(){
EmpBiz empBiz = new EmpBiz();
Object[] conditions = {"CLERK",1000.0};
List<Emp> list = empBiz.findEmpsByConditions(conditions);
list.forEach(System.out::println);
}
示例5使用setParameter()方法为参数赋值,查出职位是CLERK
且薪资在1000以上的员工.
2. setProperties()方法,绑定命名参数与一个对象的属性值
setProperties()方法用法示例代码如下:
示例6:
需求说明:使用setProperties()方法以及命名参数的方式实现示例5的查询.
EmpDao.java
关键代码如下:
public List<Emp> findByConditions(Emp conditions){
String hql = "from Emp where job=:job and salary>:salary";
Query<Emp> query = currentSession().createQuery(hql, Emp.class);
query.setProperties(conditions);
return query.list();
}
EmpBiz.java
关键代码如下:
public List<Emp> findEmpsByConditions(Emp conditions) {
Transaction transaction = null;
List<Emp> list = null;
try {
transaction = empDao.currentSession().beginTransaction();
list = empDao.findByConditions(conditions);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法关键代码如下:
EmpBiz empBiz = new EmpBiz();
Emp emp = new Emp();
emp.setJob("CLERK");
emp.setSalary(1000.0);
List<Emp> list = empBiz.findEmpsByConditions(emp);
list.forEach(System.out::println);
以上代码使用了setProperties()
方法为命名参数赋值,使用Emp
对象封装了条件,命名参数和Emp
类的相关属性相匹配,setProperties()
方法即可根据参数名获取对象中相关属性值完成赋值.
参数绑定对null是安全的,以下代码不会抛出异常:
String name = null;
String hql = "from Dept where deptName = :name";
Query query = session.createQuery(hql,Dept.class);
query.setParameter("name",name);
deptList = query.list();
以上HQL语句对应的SQL查询语句如下:
select * from DEPT where DNAME = null
但通常情况下,查询结果并不是我们所期望的,在SQL中null与其它任何值的比较都不为真.因此如果希望查询部门为null的数据,应该使用dept.deptName is null.
2.3 实现动态查询
在查询条件很多的情况下,传递过多的参数很不方便.此时,可以把参数封装在对象中,再使用之前所述的Query
接口的setProperties()
方法为 HQL 中的命名参数赋值.setProperties()
方法会把对象的属性匹配到命名参数上,需要注意的是命名参数的名称要与Java对象的属性匹配.
需求说明:查找符合以下条件的员工信息:
(1). 职位是店员,如 job = “CLERK” ;
(2). 工资大于1000元,如 salary > 1000;
(3). 入职时间在 1981年4月1日 至 1985年9月9日之间.
注意:以上三个条件可以任意组合
查询的条件最多有3个,根据用户实际输入确定.在条件个数不确定的情况下,可以使用Hibernate
提供的动态设置查询参数的方式,这里通过新建一个类来封装查询条件.如示例7所示:
示例7:
封装查询条件的EmpCondition.java
类中的关键代码如下:
/**
* 封装查询需要的参数
*/
public class EmpCondition {
/**员工职位*/
private String job;
/**员工薪水*/
private Double salary;
/**员工入职开始时间*/
private Date hireDateStart;
/**入职结束时间*/
private Date hireDateEnd;
//省略setter和getter
}
EmpDao.java
中关键代码如下:
public List<Emp> findByConditions(String hql, EmpCondition conditions) {
Query<Emp> query = currentSession().createQuery(hql, Emp.class);
query.setProperties(conditions);
return query.list();
}
EmpBiz.java
中关键代码如下:
public List<Emp> findEmpsByConditions(EmpCondition conditions) {
Transaction transaction = null;
List<Emp> list = null;
try {
transaction = empDao.currentSession().beginTransaction();
//HQL根据动态条件生成
StringBuilder hql = new StringBuilder("from Emp where 1=1");
if(conditions.getJob()!=null && conditions.getJob().length()>0){
hql.append(" and job=:job");
}
if(conditions.getSalary() != null && conditions.getSalary() > 0){
hql.append(" and salary >:salary");
}
if(conditions.getHireDateStart() != null){
hql.append(" and hireDate > :hireDateStart");
}
if(conditions.getHireDateEnd() != null){
hql.append(" and hireDate < :hireDateEnd");
}
list = empDao.findByConditions(hql.toString(),conditions);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法代码如下:
@Test
public void testFindEmpsByConditions_EmpCondition() throws Exception{
EmpBiz empBiz = new EmpBiz();
EmpCondition empCondition = new EmpCondition();
empCondition.setJob("CLERK");
empCondition.setSalary(1000.0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
empCondition.setHireDateStart(sdf.parse("1981-4-1"));
empCondition.setHireDateEnd(sdf.parse("1985-9-9"));
List<Emp> list = empBiz.findEmpsByConditions(empCondition);
list.forEach(System.out::println);
}
控制台生成的SQL语句如下:
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_,
emp0_.ENAME as ENAME2_1_,
emp0_.JOB as JOB3_1_,
emp0_.SAL as SAL4_1_,
emp0_.HiREDATE as HiREDATE5_1_
from
EMP emp0_
where
1=1
and emp0_.JOB=?
and emp0_.SAL>?
and emp0_.HiREDATE>?
and emp0_.HiREDATE<?
输出结果如下:
Emp{empNo=7934, empName='MILLER', job='CLERK', salary=1300.0, hireDate=1982-01-23 00:00:00.0}
修改测试代码,忽略第4个条件也就是入职结束时间,测试方法关键代码如下:
@Test
public void testFindEmpsByConditions_EmpCondition() throws Exception{
EmpBiz empBiz = new EmpBiz();
EmpCondition empCondition = new EmpCondition();
empCondition.setJob("CLERK");
empCondition.setSalary(1000.0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
empCondition.setHireDateStart(sdf.parse("1981-4-1"));
//empCondition.setHireDateEnd(sdf.parse("1985-9-9"));
List<Emp> list = empBiz.findEmpsByConditions(empCondition);
list.forEach(System.out::println);
}
执行程序,生成的SQL语句如下:
Hibernate:
select
emp0_.EMPNO as EMPNO1_1_,
emp0_.ENAME as ENAME2_1_,
emp0_.JOB as JOB3_1_,
emp0_.SAL as SAL4_1_,
emp0_.HiREDATE as HiREDATE5_1_
from
EMP emp0_
where
1=1
and emp0_.JOB=?
and emp0_.SAL>?
and emp0_.HiREDATE>?
控制台输出结果如下:
Emp{empNo=7876, empName='ADAMS', job='CLERK', salary=1100.0, hireDate=1987-07-13 00:00:00.0}
Emp{empNo=7934, empName='MILLER', job='CLERK', salary=1300.0, hireDate=1982-01-23 00:00:00.0}
2.4 使用uniqueResult()
方法获取唯一结果
上面使用Query
接口的提供的list()
,iterate()
方法获取查询结果集合,还可以使用uniqueResult()
方法获取唯一的结果.如示例8所示:
示例8:
EmpDao.java
关键代码如下:
public Long obtainTheRowCount(Double sal){
String hql = "select count(id) from Emp where salary >= :sal";
return currentSession().createQuery(hql,Long.class).setParameter("sal",
sal).uniqueResult();
}
EmpBiz.java
关键代码如下:
public Long countBySalary(Double salary) {
Transaction transaction = null;
Long result = null;
try {
transaction = empDao.currentSession().beginTransaction();
result = empDao.obtainTheRowCount(salary);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return result;
}
测试方法的关键代码如下:
@Test
public void testCountBySalary() throws Exception{
EmpBiz empBiz = new EmpBiz();
Long result = empBiz.countBySalary(1000.0);
System.out.println(result);
}
需要注意的是:当查询结果不唯一时,不能使用query.uniqueResult()
方法,否则会发生一下错误:org.hibernate.NonUniqueResultException:query did not return a unique result 3;
3. 分页和投影
3.1 实现数据分页查询
在之前的学习过程中,分页需要使用复杂的SQL语句实现.Hibernate提供了简便的方法实现分页,即通过使用Query
接口的setFirstResult(int firstResult)
方法和setMaxResults(int maxResults)
方法实现.顾名思义,setFirstResult()
用于设置需要返回的第一条记录的位置(位置下标从0开始),setMaxResults()
用于设置最大返回记录条数.
具体步骤如下所指示:
(1) 使用聚合函数count()
获取总记录数count.
(2) 计算总页数,代码如下:
//pageSize用于保存每页显示的记录数
int totalPages = count % pageSize == 0 ? count/pageSize : count/pageSize + 1;
(3) 实现分页,语句如下:
//pageIndex用于保存当前页码
query.setFirstResult((pageIndex-1)*pageSize);
query.setMaxResult(pageSize);
List result = query.list();
按照以上的步骤,查询雇员信息,按雇员编号升序排序,每页显示2条记录,获取第1页雇员信息.如示例9所示.
示例9:
EmpDao.java
关键代码如下:
public List<Emp> findByPages(int pageNo,int pageSize){
String hql = "from Emp order by id";
return currentSession().createQuery(hql,Emp.class)
.setFirstResult((pageNo-1)*pageSize)//设置获取结果的起始下标
.setMaxResults(pageSize).list();//设置最大返回结果集
}
EmpBiz.java
关键代码如下:
public List<Emp> findEmpsByPages(int pageNo,int pageSize) {
Transaction transaction = null;
List<Emp> list = null;
try {
transaction = empDao.currentSession().beginTransaction();
list = empDao.findByPages(pageNo,pageSize);
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法代码如下:
@Test
public void testFindEmpsByPages() throws Exception{
int pageNo = 1;
int pageSize = 2;
EmpBiz empBiz = new EmpBiz();
List<Emp> list = empBiz.findEmpsByPages(pageNo,pageSize);
list.forEach(System.out::println);
}
3.2 使用投影查询
有时数据展示并不需要获取对象的全部属性,而是只需要对象的某一个或某几个属性,或者需要通过表达式,聚合函数等方式得到某些结果,此时可以使用投影查询. 投影查询需要使用**
HQL
的select
子句**.对于投影结果的封装.有以下三种常见情况:
1. 每条查询结果仅包含一个结果列
==此时,每条查询结果将作为一个
Object
对象进行引用,==如示例10所示:
示例10:
DeptDao.java
中关键代码如下:
public List<String> findAllNames(){
String hql = "select deptName from Dept";
return currentSession().createQuery(hql).list();
}
DeptBiz.java
中的关键代码如下:
public List<String> findAllEmpName() {
Transaction transaction = null;
List<String> list = null;
try {
transaction = deptDao.currentSession().beginTransaction();
list = deptDao.findAllNames();
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法关键代码如下:
@Test
public void testFindAllDeptName() throws Exception {
DeptBiz deptBiz = new DeptBiz();
List<String> list = deptBiz.findAllDeptName();
list.forEach(System.out::println);
}
获得所有部门的名称,查询结果集合中的每个对象都是deptName
字符串
2. 每条查询结果包含不止一个结果列
==此时,每条查询结果将被封装成
Object
数组.==如示例11所示:
示例11:
DeptDao.java
中的关键代码如下:
public List<Object[]> findAllDeptList(){
String hql = "select deptNo,deptName from Dept";
return currentSession().createQuery(hql).list();
}
业务层中关键代码如下:
public List<Object[]> findAllDeptList() {
Transaction transaction = null;
List<Object[]> list = null;
try {
transaction = deptDao.currentSession().beginTransaction();
list = deptDao.findAllDeptList();
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法关键代码如下:
@Test
public void testFindAllDeptList() throws Exception {
DeptBiz deptBiz = new DeptBiz();
List<Object[]> list = deptBiz.findAllDeptList();
for (Object[] dept : list) {
System.out.println(dept[0] + ":" + dept[1]);
}
}
示例11获得所有部门的编号和名称.查询结果集合中每个元素都是对象数组Object[]
,数组长度是2,数组的第一个元素是部门编码,第二个元素是部门名称.
这种方式一般应用于查询部分属性值时,注意数组下标与属性值对应
3. 将每条查询结果通过构造方法封装成对象
如果希望以对象的方式使用查询结果,可以采用如示例12所示的方法.
示例12:
DeptDao.java
中的关键代码:
public List<Dept> findNewDeptList() {
//要求 Dept 类中有Dept(Byte deptNo,String deptName) 和 Dept()构造方法
String hql = "select new Dept(deptNo,deptName) from Dept";
return currentSession().createQuery(hql).list();
}
业务层关键代码如下:
public List<Dept> findNewDeptList() {
Transaction transaction = null;
List<Dept> list = null;
try {
transaction = deptDao.currentSession().beginTransaction();
list = deptDao.findNewDeptList();
transaction.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
}
return list;
}
测试方法关键代码如下:
@Test
public void testFindNewDeptList() throws Exception {
DeptBiz deptBiz = new DeptBiz();
List<Dept> list = deptBiz.findNewDeptList();
list.forEach(System.out::println);
}
注意:使用这种方式时,需添加Dept对应的有参跟无参构造方法
这种方式使返回的结果更加符合面向对象的风格.需要注意的是,这样查询得到的
Dept
对象不是持久化的,不能借助Hibernate
的缓存机制实现与数据库的同步,仅用于封装本次查询结果.
封装投影查询的结果,除了使已有的持久化类,也可以针对查询结果的特点,另外定义一个JavaBean,专门封装查询结果
经验:实际查询中经常通过表达式,聚合函数等得到计算列这样的特殊结果,或者查询的目的仅仅是为了展示,不需要保持持久化状态以及维护数据,这样的情况都应该使用投影查询,以减少开销,提高效率
4. 使用IDEA反向工程工具
4.1 添加Hibernate支持
项目右击–>Add Framework Support
4.2 选择对应的功能
选中生成hibernate的默认配置文件和数据库的计划
4.3 填写对应的信息
如图填写包名 , 并选中生成实体类以及映射文件
4.4 点击Yes,反向生成实体类和hibernate的配置文件