SSH Chapter 05 HQL 实用技术

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类和属性的名称外,查询语句对大小写不敏感,所以SELECTselect是相同的,但是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点不一样:

  1. 返回的类型不一样,list()返回List, iterate()返回Iterator

  2. 获取数据的方式不一样,list()会直接查数据库, iterate()会先到数据库中把id都取出来,然后真正要遍历某个对象的时候先到缓存中找.
    如果找不到,以id为条件再发一条sql到数据库,这样如果缓存中没有数据,则查询数据库的次数为n+1。

  3. iterate会查询2级缓存,list只会查询一级缓存。

  4. 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 使用投影查询

有时数据展示并不需要获取对象的全部属性,而是只需要对象的某一个或某几个属性,或者需要通过表达式,聚合函数等方式得到某些结果,此时可以使用投影查询. 投影查询需要使用**HQLselect子句**.对于投影结果的封装.有以下三种常见情况:

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的配置文件

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值