Fork me on GitHub

Hibernate框架学习(四)

Hibernate Framework

简介:
Hibernate框架是一个全自动的ORM框架,它拥有强大的映射功能,提供了缓存机制、事务管理、拦截器机制、查询语句的多方面支持

本系列笔记可能会遇到的专业词汇有:

  • Framework, 框架,某一类问题的总体解决方案

  • ORM, Object Relationship Mapping, 对象关系映射

  • DATABASE, 数据库,存储数据的一种方式

  • HQL, Hibernate Query Language, Hibernate查询语句

  • Transaction, 事务,一组相关的操作

  • Session, 会话

本系列笔记包含如下的课程内容:

  • Hibernate框架原理和开发流程
  • 框架缓存机制
  • 对象关系映射
  • 框架提供的查询机制
    • 基于底层的SQL查询机制
    • 基于HQL查询机制
    • 基于Criteria查询机制

Hibernate 查询体系

logo

此章节重点讲解 Hibernate框架的查询体系

章节重点:

  • HQL查询
  • QBC查询
  • SQL查询
  • 调试

章节难点:

  • Criteria查询
  • HQL高级查询

Hibernate框架提供了一个抽象层次的查询体系,如下:

图示

其中,最底层的SQL查询基本上就是JDBC级别的封装,而Criteria接口采用了面向对象的操作方式,程序员无需写任何的SQL或类SQL语句,直接通过方法来生成SQL语句,抽象层次较高。

HQL查询

HQL, 全称:Hibernate Query Language, Hibernate查询语句,它具备如下特性:

  • 基于SQL语法;
  • 面向对象的查询语言;
  • 使用可命名参数;
  • 提供更简单的分页查询操作;
  • 如果有select *,则必须省略.

如:

1
2
3
String hql = "from User as u where u.name like :name";

String hql = "from User as u where u.age between ? and ?";

在Hibernate框架中,使用org.hibernate.Query 接口来执行HQL字符串,它是以面向对象的方式来陈述的查询接口
通过 Session 接口的 createQuery(String hql)方法,来获取一个Query实例

1
2
String hql = "from Department"; //查询出所有的部门
Query q = ses.createQuery(hql); //创建Query实例

Query接口的常用方法如下:

  • list() 方法, 将查询的结果封装成一个List集合对象,不带泛型,需要强制类型转换
  • uniqueResult() 方法,将查询的结果封装为单个对象,如果不存在,则返回null
  • setParameter(String param, Object value) 方法,用来为hql中指定的动态参数设值
  • setFirstResult(int first) 方法,设置本次查询的第一条记录的下标(初始值是0)
  • setMaxResults(int max) 方法,设置本次查询最多返回max条数据

HQL原理

在开发时,程序员专注于面向对象的编辑 HQL 语句;
在运行时,Hibernate 自动将 HQL 翻译成相应的 SQL 并执行。

图示

HQL语法

一个 HQL 语句可以包含关键字、目标、别名和函数。

  • 关键字
    • 由Hibernate规定,它具有特殊语义,HQL大部分关键字都来自于SQL。
    • 比如: select、from、where等。
  • 目标
    • 是HQL处理的内容,它可以是类、id属性、普通属性和关系属性。
  • 别名
    • 是指向当前目标的引用,使用它可以在HQL访问当前的目标。
    • 比如 :User u 、 u.name等。
  • 函数
    • 可以对某个目标实施某种运算或处理,比如求和,取最大值等。
    • 比如: max(u.age),sum(emp.salary)等。

HQL结构

图示

  • from 关键字
    • 表示查询指定的实体对象
    • 如: from User as u 或 from User u 或 from User
  • select 关键字
    • select 语句决定了from语句中的哪些对象将出现在返回的结果集中,如果不使用select,结果集中将包含from语句列出的所有对象。
    • 如: select c.name,c.address from Customer as c
    • 如果需要排重,可以使用 distinct 关键字
  • select 函数
    • select 语句中可以使用函数,对某些属性统计并返回结果。
    • 常用的函数有: count(…), avg(…),sum(…),min(…),max(…),与SQL中的基本一致。
    • 如:select count(*) from Customer c
    • 注意:以上都使用uniqueResult()方法获得,返回类型是Object,可以通过 debug 来确定数据类型
  • where 关键字
    • where关键字用来限定哪些对象将会包含在返回的列表中,通常使用”类名别名.属性名”的方法
    • 如: from Customer c where c.name like ?
    • 如: from Contact con where con.customer.name = ?
  • 运算符
    • 算数运算符
      • +,-, *, /
    • 比较运算符
      • =,>=,<=,<>,!=,like,between,not between
    • 逻辑运算符
      • and, or, not
    • 空运算符
      • is null, is not null, is empty, is not empty
    • 包含运算符
      • in, not in, member of, not member of
  • order by
    • order by语句可以将返回的结果按照类的某个属性进行排序,可以使用asc或desc指定升序或降序,如果不写默认为asc。
    • 如: from Contact con order by con.name
    • 如: from Contact con order by con.age desc
  • join fetch
    • 使用 join fetch 可以让 Hibernate 在查询时也提取关联对象的信息,不要再进行延迟加载。
    • 如:from Customer c join fetch c.contactSet where c.level > :level

常用的HQL案例

  • 根据用户名来查询用户
    from User as u where u.name = :name
  • 根据客户名来查询订单
    from Order as o where o.customer.name = :name //使用了隐式关联
  • 根据订单下单时间来查询订单
    from Order as o where o.order_date between ? and ? //order_date表示下单时间
  • 查询出用户名及用户的角色名
    select u.name, r.name from User as u join u.roles as r //显示关联
  • 根据用户名来查询属于此用户角色信息
    select r from Role r join r.users u where u.name = :name //使用select指定只查角色

以上都是常见的HQL语法,在HQL中,还支持子查询和投影查询

子查询

1
2
3
//子查询的HQL案例
//查询出工资超公司平均工资的员工
String hql = "from Emp as e where e.salary > select avg(salary) from Emp";

投影查询

1
2
//使用select来投影指定的列,并且通过Hibernate特殊语法来封装成java对象
String hql = "select new com.tz.view.CustView(c.name,count(o.id) from Customer c join c.orders o order by count(o.id) desc"; //统计出客户与各自的订单数,并按订单数降序排序

当然,你事先得编写一个视图类 CustView, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tz.view;

public class CustView {
private String name; //客户名
private int count; //订单数

//一定要提供与投影查询一致的构造方法
public CustView(String name, int count) {
this.name = name;
this.count = count;
}
//setter/getter省略
...
}

HQL处理UPDATE和DELETE

语法: ( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?

好处:这种方式删除指定的记录比通过对象的方式删除要高效得多。

1
2
3
4
5
6
7
8
9
10
11
//更新满足条件的客户名称
Session session = getSession();
Transaction tx = ses.beginTransaction();
//定义HQL
String hql = "update Customer as c where c.name = :newName where c.name = :oldName";
int updatedEntities = session.createQuery(hql)
.setString("newName", newName)
.setString("oldName", oldName)
.executeUpdate();
tx.commit();
session.close();
1
2
3
4
5
6
7
8
9
10
//如果要更新版本的时间戳,则可以:
Session session = getSession();
Transaction tx = session.beginTransaction();
String hql = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = session.createQuery(hql)
.setString("newName", newName)
.setString("oldName", oldName)
.executeUpdate();
tx.commit();
session.close();
1
2
3
4
5
6
7
8
9
//删除操作
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hql = "delete Customer c where c.name = :oldName";
int deletedEntities = session.createQuery(hql)
.setString("oldName", oldName)
.executeUpdate();
tx.commit();
session.close();

分页查询

分页查询很简单,无需我们编写底层的分页查询SQL,而只需要调用如下的方法即可

  • setFirstResult(int first)
  • setMaxResults(int max)

为什么要使用分页?

铁律一:
只要存在实体类的列表页面,通常都会做分页处理.
原因:
1)提高查询效率,只需查询一小部分数据;
2)优化用户体验,无需面对海量数据,就像翻书一样浏览数据。

铁律二:
在分页基础上,还需提供搜索功能,进一步改善用户体验.
原因:
想象一个没有搜索功能的淘宝首页….

案例代码

1
2
3
4
5
6
7
8
//分页显示图书信息,每页显示 10行, 如果要查第2页,则
String hql = "from Book as b";
Query q = ses.createQuery(hql);
//调用方法指定分页
q.setFirstResult((2-1)*10); //指定起始位置,第2页,就是从第10行开始,【因为每页10行】
q.setMaxResults(10); //至多只查询出10条记录
//
q.list();

以上的查询,生成的sql语句中,会根据不同的DB平台来生成分页的语法,而程序员们无需考虑底层的分页语法.

Criteria查询

org.hibernate.Criteria 是一个简单易用的,通过约束条件来进行查询的接口。可以轻松的解决类似搜索页面(其中存在N个搜索条件,且存在超过2的N次方的组合情况)的需求问题

通过Criteria接口进行查询,也叫 Query By Criteria, 简称QBC, 它是最高层次的查询抽象,程序员无需编写任何的查询字符串,而是采用方法调用来生成SQL。

通过 Session接口的 createCriteria(Class c) 来创建Criteria实例.

1
2
3
4
5
6
//示例
Criteria c = ses.createCriteria(User.class, "u"); //相当于 from User as u
//添加条件
c.add(Restrictions.eq("u.name","jack")); //相当于: where u.name = 'jack'
//执行
c.list(); //与 Query一样

Criteria原理

Hibernate 自动将 Criteria 翻译成相应的 SQL 并执行。

图示

Criteria常用API

图示

从上面的类图中可以看出,几个核心的接口和类是:

  • Criterion 添加where条件的准则接口,通过Restrictions工厂类可以获取此接口的实例
  • Restrictions 限制类,用来生成where条件
  • Projection 添加投影查询、分组查询的接口,通过Projections工厂类可以获取此接口的实例
  • Projections 工厂类,用来生成投影、分组的实例
  • Order 排序类

其它的类型都是派生于Criterion接口和Projection接口

Criteria接口中有如下方法:

图示

Restrictions类有如下方法:

图示

Projections类有如下方法:

图示

Order类有如下方法:

图示

详情可以查看 [Criteria] 的在线API
详情可以查看 [Restrictions] 的在线API
详情可以查看 [Projections] 的在线API
详情可以查看 [Order] 的在线API

代码案例

1
2
3
4
5
6
7
8
9
//统计客户总数
//1.创建Criteria对象
Criteria c = ses.createCriteria(Customer.class);
//添加count分组函数
c.setProjection(Projections.rowCount());
//执行
Long count = (Long)c.uniqueResult();
//
...
1
2
3
4
5
6
7
8
9
10
11
//根据客户id来查询属于此客户的订单 
//1. 创建Criteria对象
Criteria c = ses.createCriteria(Order.class,"o");
//2. 添加与Customer的关联
c.createAlias("o.customer", "c");
//3. 添加条件
c.add(Restrictions.eq("c.id",id));
//4. 执行
List<Order> orders = (List<Order>)c.list();
//
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//动态多条件查询
public List<Order> query(String ordNo,double min, double max, Date start, Date end) {
...
//1.创建Criteria
Criteria c = ses.createCriteria(Order.class, "o");
//2. 添加条件
if(ordNo != null && ordNo.trim().length() > 0) {
c.add(Restrictions.eq("o.ordNo", ordNo));
}
if(min > 0) {
c.add(Restrictions.gt("o.cost",min));
}
if(max > 0 && max > min) {
c.add(Restrictions.lt("o.cost", max));
}
if(start != null) {
c.add(Restrictions.ge("o.ship_date",start));
}
if(end != null) {
c.add(Restrictions.le("o.ship_date", end));
}
//...
//执行
return c.list();
}

子查询

在HQL中,可以使用子查询的语法,在Criteria查询中,也可以使用子查询,不过这里要引入另一个类:DetachedCriteria, 有关此类的详细说明,可以查看[DetachedCriteria] API

DetachedCriteria,也叫离线的Criteria,也就是此类的实例可以不由session进行创建,而利用Criteria的准则来构建SQL,待到要执行之时,再关联上session即可,所以,利用这个原理,可以实现子查询的功能。

下面,来看一下DetachedCriteria的API介绍和方法列表:

图示

可以看出,这个类实现了CriteriaSpecification接口,而Criteria则是此接口的子接口,换句话说,CriteriaImpl类也实现了这个接口,所以,DetachedCriteria的操作方法与Criteria几乎一样。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//利用 DetachedCriteria构建一个“查询”: 平均订单费用
DetachedCriteria dc = DetachedCriteria.forClass(Order.class,"o");
//添加查询的要求
dc.setProjection(Projections.avg("o.cost")); //求订单的平均费用
//当然,如果还有其它的要求或条件,也可以同 Criteria 一样,添加各种Restrictions或Projections

//现在,如何来使用这个子查询?
//1. 创建Criteria
Criteria c = ses.createCriteria(Order.class,"ord");
//添加条件: cost 大于 平均订单费用
//注:此处要把 "ord.cost" 这个属性转换成 Property 实例,因为Restrictions不能做子查询的比较
Property pro = Property.forName("ord.cost"); //
//再利用 Property的方法进行子查询的比较
c.add(p.gt(dc)); //连在一起写就是: c.add(Property.forName("ord.cost").gt(dc));
//最后,执行 c 即可
c.list();
//...

下面,给出 Property 类的API片断如下:

图示

当然,DetachedCriteria 也可以单独使用,只是在调用时,必需要把它与session关联,这样才可以执行。

如:

1
2
3
4
5
6
DetachedCriteria dc = DetachedCriteria.forClass(Customer.class,"c");
//...
//如要执行, 与 session关联即可
Criteria c = dc.getExecutableCriteria(session);
//后面的操作就与 Criteria 一样了
//...

SQL查询

用来执行本地化的sql语句的接口,在Hibernate查询体系中,抽象的层次最低,但有较高的查询效率。
通过 Session.createSQLQuery(String sql) 来创建

1
2
3
4
//基本例子
String sql = "select * from tbl_customer";
List list = ses.createSQLQuery(sql).addEntity(Customer.class).list();
//...

标量查询

1
2
3
4
5
6
//查询客户的name,phone,id 三个列值
String sql = "select c.name,c.phone,c.id from tbl_customer";
//...
//...
List<Object[]> results = ses.createSQLQuery(sql).list();
//这种情况下,返回的集合中的元素类型一定是 Object[]
1
2
3
4
5
6
//添加条件
String sql = "select c.id, c.name,c.phone from tbl_customer where c.name like ?";
//
ses.createSQLQuery(sql)
.setParameter(0, "jack%");
// 与上面一样,返回的也将是 对象数组的集合

实体查询

1
2
3
4
5
6
7
//查询所有的客户
String sql = "select * from tbl_customer";
//...
List<Customer> customers = ses.createSQLQuery(sql)
.addEntity(Customer.class)
.list();
//由于使用 addEntity方法添加了Customer类做为查询的结果,所以,返回的集合中的元素类型就是Customer

关联查询

1
2
3
4
5
6
7
8
String sql = "select {o.*},{c.*} from hbm_order o, hbm_customer c where o.customer_id = c.id";
Session ses = HibernateUtil.getSession();
Transaction tx = ses.beginTransaction();
//
SQLQuery query = ses.createSQLQuery(sql);
query.addEntity("o",Order.class)
.addJoin("c", "o.customer");
//此方式,采用多对一的关联,查询出来的结果集合的内容是Object[], 其中,第1个元素是Order,第2个元素是Customer
1
2
3
4
5
6
7
8
String sql = "select {o.*},{c.*} from hbm_order o, hbm_customer c where o.customer_id = c.id";
Session ses = HibernateUtil.getSession();
Transaction tx = ses.beginTransaction();
//
SQLQuery query = ses.createSQLQuery(sql);
query.addEntity("c",Customer.class)
.addJoin("o", "c.orders");
//此方式,采用一对多的关联,查询出来的结果集合的内容是Object[], 其中,第1个元素是Customer,第2个元素是Order

本章小节

  • 熟练掌握HQL的查询
  • 熟练掌握Criteria的查询
  • 了解SQLQuery的查询

参考API

criteria
Restrictions
Projections
Order
DetachedCriteria