Hibernate学习笔记 | 详解Hibernate检索方式

Hibernate检索方式

Hibernate提供了以下几种检索对象的方式

  • 导航对象图检索方式:根据已经加载的对象导航到其他对象
    例如前面我们学到的通过Customer类来获取集合的方式来获取Order对象
  • OID检索方式:按照对象的OID来检索对象
  • HQL检索方式:使用面向对象的HQL查询语言
  • QBC检索方式:使用QBC API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。
  • 本地SQL检索方式:使用本地数据库的SQL查询语句。

HQL检索方式

HQL是面向对象的查询语言,在Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。它有如下功能:

  • 在查询语句中设定各种查询条件
  • 支持投影查询,即仅检索出对象的部分属性
  • 支持分页查询
  • 支持连接查询
  • 支持分组查询,运行使用HAVING和GROUP BY关键字
  • 提供内置聚集函数,如sum()min()max()
  • 支持子查询
  • 支持动态绑定参数
  • 能够调用用户定义的SQL函数或标准的SQL函数

HQL检索方式包括以下步骤:

  • 通过Session的createQuery()创建一个Query对象,它包括一个HQL查询语句,HQL查询语句中可以包含命名参数。
  • 动态绑定参数
  • 调用Query的相关方法执行查询语句

Query接口支持方法链编程风格,它的setParameter()方法返回自身实例,而不是void类型

绑定参数

  • Hibernate的参数绑定机制依赖于JDBC API中的PreparedStatement的预定义SQL语句功能。
  • HQL的参数绑定有两种形式
    按参数名字绑定:在HQL查询语句中定义命名参数,命名参数以:开头。
    按参数位置绑定:在HQL查询语句中用?编号来定义参数位置。
  • 相关方法
    setParameter():绑定任意类型的参数,该方法的第三个参数显式指定Hibernate映射类型。
    setEntity():把参数与一个持久化类绑定。但该方法在Hibernate5中已过时,推荐使用第一个方法setParameter()
  • HQL采用ORDER BY关键字对查询结果排序。

假设有两张数据表如下:
department表和employees表为一对多关系。



两个实体类如下:

package com.cerr.hibernate.entities;

import java.util.Set;

public class Department {
    private Integer id;
    private String name;
    private Set<Employee> emps;

    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 < Employee > getEmps() {
        return emps;
    }

    public void setEmps(Set < Employee > emps) {
        this.emps = emps;
    }


}

package com.cerr.hibernate.entities;

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 +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", email='" + email + '\'' +
                ", dept=" + dept +
                '}';
    }
}

可以使用?编号来作为参数:

@org.junit.Test
    public void test(){
        //创建Query对象
        String hql = "FROM Employee WHERE salary > ?0 AND email LIKE ?1 ";
       
        Query query = session.createQuery(hql);
        Department dept = session.get(Department.class,1);
        //绑定参数
        query.setParameter(0,(float)6000).setParameter(1,"%9%");  
        //查询
        List<Employee> emps = query.list();
        System.out.println(emps.size()+":"+emps);
    }

也可以使用:标识符作为参数,还可以将实体类也作为参数(前提是存在映射关系)

@org.junit.Test
    public void test(){
        //创建Query对象
        String hql = "FROM Employee WHERE salary > :sal AND dept = :d AND email LIKE :ema ";
        Query query = session.createQuery(hql);
        Department dept = session.get(Department.class,1);
        //绑定参数
        query.setParameter("sal",(float)6000).setParameter("ema","%%").setParameter("d",dept);
        //查询
        List<Employee> emps = query.list();
        System.out.println(emps.size()+":"+emps);
    }

分页查询

  • setFirstResult(int firstResult)
    设定从哪个对象开始检索,参数firstResult表示这个对象在查询结果中的索引位置,索引位置的起始值为0,默认情况下,Query从查询结果中的第一个对象开始检索

  • setMaxResults(int maxResults)
    设定一次最多检索出的对象的数目。在默认情况下,QueryCriteria接口检索出查询结果中所有的对象。

    @org.junit.Test
    public void testPageQuery(){
        String hql = "FROM Employee";
        Query query = session.createQuery(hql);
        int pageNo = 3;//页数
        int pageSize = 2;//每页最大数量
        //查询第pageNo页的pageSize条记录
        List<Employee> employees = query.setFirstResult((pageNo-1)*pageSize).setMaxResults(pageSize).list();
        System.out.println(employees);
    }

在映射文件中定义命名查询语句

  • Hibernate允许在映射文件中定义字符串形式的查询语句。
  • <query>元素用于定义一个HQL查询语句,它和<class>元素并列。
  • 在程序中通过SessiongetNameQuery()获取查询语句对应的Query对象。

我们在Employee.hbm.xml文件中定义命名查询语句<query name="salaryEmps"><![CDATA[FROM Employee WHERE salary > :minSalary AND id < :maxID ]]></query>
然后测试类代码:

    @org.junit.Test
    public void testNameQuery(){
        Query query = session.getNamedQuery("salaryEmps");
        List<Employee> employees = query.setParameter("minSalary",(float)5000).setParameter("maxID",4).list();
        System.out.println(employees);
    }

投影查询

  • 查询结果仅包含实体的部分属性,可以通过SELECT关键字实现。

  • Querylist()返回的集合中包含的是数组类型的元素,每个对象数组代表查询结果的一条记录。

  • 可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录。

  • 可以通过DISTINCT关键字来保证查询结果不会返回重复元素。

        /**
     * 返回一个对象的集合
     */
    @org.junit.Test
    public void testFieldQuery2(){
        String hql = "SELECT new Employee(salary,email) FROM Employee WHERE dept = :dept ";
        Query query = session.createQuery(hql);
        Department dept = new Department();
        dept.setId(1);
        List<Employee> result = query.setParameter("dept",dept).list();
        //打印
        for(Employee emp : result){
            System.out.println(emp.getEmail());
        }
    }

    /**
     * 返回一个字符串的集合
     */
    @org.junit.Test
    public void testFieldQuery(){
        String hql = "SELECT email,salary FROM Employee WHERE dept = :dept ";
        Query query = session.createQuery(hql);
        Department dept = new Department();
        dept.setId(1);
        List<Object[]> result = query.setParameter("dept",dept).list();
        //打印
        System.out.println(result);
        for (Object[] res : result){
            System.out.println(Arrays.asList(res));
        }
    }

注意在使用返回对象集合的时候,在实体类中需要有对应的构造器方法,并且如果加的是有参的构造器,那么一定要加上一个无参构造器。

报表查询

报表查询用于对数据分组和统计,与SQL一样,HQL利用GROUP BY关键字对数据分组,用HAVING关键字对分组数据设定约束条件。

在HQL查询语句中可以调用以下的聚集函数

  • count()
  • min()
  • max()
  • sum()
  • avg()

示例:查询最低工资(大于5000)和最高工资

    @org.junit.Test
    public void testGroupBy(){
        String hql = "SELECT min(salary),max(salary) FROM Employee GROUP BY dept HAVING min(salary) >:minSal";
        Query query = session.createQuery(hql).setParameter("minSal",(float)5000);
        List<Object[]> res = query.list();
        for (Object[] r : res){
            System.out.println(Arrays.asList(r));
        }
    }

HQL的(迫切)左外连接

迫切左外连接

  • LEFT JOIN FETCH关键字表示迫切左外连接检索策略
  • list()返回的集合中存放实体对象的引用。每个Department对象关联的Employee集合都被初始化,存放所有关联的Employee的实体对象。
  • 查询结果中可能会包含重复元素,可以通过一个HashSet来过滤重复元素。也可以在查询语句中加入distinct关键字。

左外连接

  • LEFT JOIN关键字表示左外连接查询
  • list()返回的集合中存放的是对象数组类型
  • 根据配置文件来决定Employee集合的检索策略
  • 如果希望list()返回的集合仅仅包含Department对象,可以在HQL查询语句中使用SELECT关键字。
//左外连接
    @org.junit.Test
    public void testLeftJoin(){
        String hql = "SELECT distinct d FROM Department d LEFT JOIN d.emps";
        Query query = session.createQuery(hql);
        //加上SELECT后
        List<Department> res = query.list();
        for (Department department : res){
            System.out.println(department);
        }
        //没加SELECT
//        List<Object[]> result = query.list();
//        System.out.println(result);
//        for (Object[] objects : result){
//            System.out.println(Arrays.asList(objects));
//        }
    }

    //迫切左外连接
    @org.junit.Test
    public void testLeftJoinFetch(){
        String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps";
        Query query = session.createQuery(hql);
        List<Department> departments = query.list();
        System.out.println(departments.size());
        for (Department d : departments){
            System.out.println(d.getId()+" "+d.getEmps().size());
        }
    }

HQL(迫切)内连接

迫切内连接

  • INNER JOIN FETCH关键字表示迫切内连接,也可以省略INNER关键字
  • list()返回的集合中存放实体类(Department)对象的引用,每个实体类对象的关联的集合(Employee集合)都被初始化,存放所有关联的Employee对象。

内连接:

  • INNER JOIN关键字表示内连接,也可以省略INNER关键字
  • list()的集合中存放的每个元素对应查询结果的一条记录,每个元素都是对象数组类型
  • 如果希望list()返回的集合仅仅包含Department对象,可以在HQL查询语句中使用SELECT关键字。
    //内连接
    @org.junit.Test
    public void testLeftJoin(){
        String hql = "SELECT distinct d FROM Department d INNER JOIN d.emps";
        Query query = session.createQuery(hql);
        //加上SELECT后
        List<Department> res = query.list();
        for (Department department : res){
            System.out.println(department);
        }
        //没加SELECT
//        List<Object[]> result = query.list();
//        System.out.println(result);
//        for (Object[] objects : result){
//            System.out.println(Arrays.asList(objects));
//        }
    }

    //迫切内连接
    @org.junit.Test
    public void testINNERJoinFetch(){
        String hql = "SELECT DISTINCT d FROM Department d INNER JOIN FETCH d.emps";
        Query query = session.createQuery(hql);
        List<Department> departments = query.list();
        System.out.println(departments.size());
        for (Department d : departments){
            System.out.println(d.getId()+" "+d.getEmps().size());
        }
    }

内连接和左外连接的区别是内连接不会返回左表不符合条件的记录,而左外连接会返回左表中不符合条件的记录,比如下面的表中


表的记录只有DEPT_ID为1的,而在departments表中有两条记录:

那么如果用上面的左外连接的代码查询Department则会查到两个部门(其中第二个部门为空),如果用上面的内连接的代码查询Department则会查询到一个部门。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章