Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)

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表为一对多关系。

Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)
文章图片

Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)
文章图片

两个实体类如下:
package com.cerr.hibernate.entities; import java.util.Set; public class Department { private Integer id; private String name; private Set 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 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 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 employees = query.setFirstResult((pageNo-1)*pageSize).setMaxResults(pageSize).list(); System.out.println(employees); }

在映射文件中定义命名查询语句
  • Hibernate允许在映射文件中定义字符串形式的查询语句。
  • 元素用于定义一个HQL查询语句,它和元素并列。
  • 在程序中通过SessiongetNameQuery()获取查询语句对应的Query对象。
我们在Employee.hbm.xml文件中定义命名查询语句 :minSalary AND id < :maxID ]]>
然后测试类代码:
@org.junit.Test public void testNameQuery(){ Query query = session.getNamedQuery("salaryEmps"); List 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 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 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 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 res = query.list(); for (Department department : res){ System.out.println(department); } //没加SELECT //List 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 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对象。
【Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)】内连接:
  • 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 res = query.list(); for (Department department : res){ System.out.println(department); } //没加SELECT //List 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 departments = query.list(); System.out.println(departments.size()); for (Department d : departments){ System.out.println(d.getId()+" "+d.getEmps().size()); } }

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

Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)
文章图片

表的记录只有DEPT_ID为1的,而在 departments表中有两条记录:
Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)
文章图片

那么如果用上面的左外连接的代码查询 Department则会查到两个部门(其中第二个部门为空),如果用上面的内连接的代码查询 Department则会查询到一个部门。
QBC检索 QBC查询就是通过使用Hibernate提供的Query By Criteria API来查询对象,这种API封装了SQL语句的动态拼装,对查询提供了更加面向对象的功能接口。
下面我们来讲下几个例子,假设有Employees表如下:

Hibernate学习笔记|Hibernate学习笔记 | 详解Hibernate检索方式(HQL,QBC等)
文章图片

其实体类如下:
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 + '}'; }public Employee(float salary, String email) { this.salary = salary; this.email = email; }public Employee() { } }

初始化与关闭:
package com.cerr.hibernate.entities; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.criterion.*; import org.hibernate.query.Query; import org.junit.After; import org.junit.Before; import java.util.Arrays; import java.util.List; public class Test { private SessionFactory sessionFactory; private Session session; private Transaction transaction; @Before public void init() throws Exception { Configuration configuration = new Configuration().configure(); sessionFactory = configuration.buildSessionFactory(); session = sessionFactory.openSession(); transaction = session.beginTransaction(); }@After public void destory() throws Exception { transaction.commit(); session.close(); sessionFactory.close(); }}

  • QBC使用Criteria对象来进行检索,可以使用其add()来添加查询条件,使用uniqueResult()list()来查询结果。
    例如单元测试如下:
@org.junit.Test public void testQBC(){ //创建Criteria对象 Criteria criteria = session.createCriteria(Employee.class); //添加查询条件 criteria.add(Restrictions.eq("email","9")); criteria.add(Restrictions.gt("salary",(float)5000)); //执行查询 Employee employee = (Employee) criteria.uniqueResult(); System.out.println(employee); }

  • 在QBC中要添加AND,可以使用Conjunction对象,要添加OR,可以使用Disjunction对象。
    单元测试如下:
@org.junit.Test public void testQBC2(){ Criteria criteria = session.createCriteria(Employee.class); //AND:使用Conjunction表示,而其本身就是一个Criteria对象。 //且其中还可以添加Criteria对象 Conjunction conjunction = Restrictions.conjunction(); conjunction.add(Restrictions.like("name","a")); Department dept = new Department(); dept.setId(1); conjunction.add(Restrictions.eq("dept",dept)); System.out.println(conjunction); //OR Disjunction disjunction = Restrictions.disjunction(); disjunction.add(Restrictions.ge("salary",(float)6000)); disjunction.add(Restrictions.isNull("email")); criteria.add(disjunction); criteria.add(conjunction); criteria.list(); }

发送的SQL查询语句如下:
select this_.ID as ID1_2_0_, this_.NAME as NAME2_2_0_, this_.SALARY as SALARY3_2_0_, this_.EMAIL as EMAIL4_2_0_, this_.DEPT_ID as DEPT_ID5_2_0_ from EMPLOYEES this_ where ( this_.SALARY>=? or this_.EMAIL is null ) and ( this_.NAME like ? and this_.DEPT_ID=? )

  • 在QBC中使用统计查询,可以通过Projections的静态方法实现。
    查询salary的最大值:
@org.junit.Test public void testQBC3(){ Criteria criteria = session.createCriteria(Employee.class); //统计查询:可以由Projections的静态方法得到 criteria.setProjection(Projections.max("salary")); System.out.println(criteria.uniqueResult()); }

  • 排序可以使用Order类的静态方法,分页可以使用Criteria的方法。
    示例:
@org.junit.Test public void testQBC4(){ Criteria criteria = session.createCriteria(Employee.class); //添加排序 criteria.addOrder(Order.asc("salary")); //升序 criteria.addOrder(Order.desc("email")); //降序 //设置翻页 int pageSize = 2; int pageNo = 3; criteria.setFirstResult((pageNo-1)*pageSize).setMaxResults(pageSize).list(); }

本地SQL查询 本地SQL查询来完善HQL不能涵盖所有的查询特性。
示例:
@org.junit.Test public void testNativeSQL(){ String sql = "INSERT INTO departments VALUES(?0,?1)"; Query query = session.createSQLQuery(sql); query.setParameter(0,3).setParameter(1,"c").executeUpdate(); }

    推荐阅读