大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: JDBC&Hibernate 修改讲义

说明:

  • Hibernate提供了多种查询方式(HQL, JPQL),但我们通常(强类型的)Criteria为主,搭配原生SQL就OK了。
  • 本章节的Criteria遵循JPA规范的javax.persistence.criteria.CriteriaQuery,而不是legacy(老的)org.hibernate.Criteria


最简单示例

目标:查出所有Teacher。

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Student> criteria = builder.createQuery(Student.class);
criteria.from(Student.class);

TypedQuery<Student> query = em.createQuery(criteria);
List<Student> students = query.getResultList();

students.forEach(s -> System.out.println(s.getName()));
CriteriaBuilder:(仍然是builder模式)
  • 通过EntityManager对象获得
  • 有一个createQuery()实例方法,该方法接受一个类型做参数,指定查询结果(包含元素)的类型,返回:

CriteriaQuery<?>:

  • 这是一个泛型对象,泛型参数类型和createQuery()方法参数类型指定类型一致
  • 其方法from():通过指定entity类型,指定查询从哪个表开始,在哪个表进行

TypedQuery<X>:

EntityManager对象(这次不是CriteriaBuilder)的createQuery()方法,接受CriteriaQuery对象作为参数,以此构建TypedQuery<X>对象。

log演示:

  • 直到此时,才开始构建SQL查询语句
  • Criteria还是调用HQL,而且有缓存(cache)机制
  • HQL视SQL语句为一个一个的节点形成的树状结构
  • 此时只是构建SQL语句,并不会进行数据库的查询

getResultList():该方法才会利用之前生成的SQL语句,查询数据库。


WHERE条件

调用CriteriaQuery的where方法,可以构建SQL语句的WHERE部分

Root<Student> root = criteria.from(Student.class);
//设置条件:WHERE
//使用比较相等:equal
//比较的是Student的属性(attribute)name
//和"fg"进行比较
criteria.where(builder.equal(root.get("name"), "fg"));

转到定义演示:where要传入的是一个Expression(表达式),

本质是对查询条件的封装:

CriteriaQuery<T> where(Expression<Boolean> restriction);
public interface Expression<T> extends Selection<T> {
public interface Predicate extends Expression<Boolean> {

Expression实例又通过CriteriaBuilder的实例方法获得(这个有点……别扭?)

传入的是Expression,返回的还是Expression,所以可以连缀

#体会:强类型查询


Metamodel

同学们注意,这样用字符串给get()方法传递attributeName是很危险(error prone,容易犯错)的:

root.get("name")
道理不用多讲了吧?^_^

利用maven引入第三方组件jpamodelgen

演示:在项目上右键-properties,找到JPA下面的:

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-jpamodelgen</artifactId>
	<version>5.1.17.Final</version>
</dependency>

选择一个目录,apply and close。

然后就会在src下面生成一系列的类,和entity同名但多一个下划线后缀:

@Generated(value="Dali", date="2021-10-21T20:56:15.279+0800")
@StaticMetamodel(Student.class)
public class Student_ extends Person_ {
	public static volatile ListAttribute<Student, Score> scores;
	public static volatile SingularAttribute<Student, Contact> contact;

背后原理:根据@Entity注解产生……,参考:

  • https://docs.oracle.com/javaee/6/tutorial/doc/gjiup.html
  • https://docs.jboss.org/hibernate/jpamodelgen/1.0/reference/en-US/html_single/#whatisit

于是,我们就可以这样给get()方法传值:

root.get(Student_.name) 

valueType的取值,使用get连缀

root.get(Student_.Contact).get(Contact_.city)



其他where条件

where()方法里面,除了可以equal(),还可以:

//大于,比较数值
builder.greaterThan(root.get(Student_.age), 17)
//大于等于greaterThan-or-equal
builder.ge(root.get(Student_.age), 17)
//小于,比较时间(注意有类型编译时检查)
builder.lessThan(root.get(Student_.enroll), LocalDate.now()
//字符串like操作
builder.like(root.get(Student_.description), "%17bang%")
//检查是不是NULL值
builder.isNull(root.get(Student_.rest))
//between
builder.between(root.get(Student_.age), 20, 30)、
//在attribute(列)之间进行比较运算
builder.equal(root.get(Student_.age), root.get(Student_.id))
//对attribute(列)进行加工(转成大写)后运算
//不提倡,因为可能会索引失效,但确实没办法的时候……
builder.equal(builder.upper(root.get(Student_.name)),"ATAI")

not,and和or

当然,还可以把这些条件再通过not,and和or组合起来,比如:

criteria.where(builder.and(
		builder.not(builder.isNull(root.get(Student_.rest))),
		builder.or(
				builder.lessThan(root.get(Student_.enroll), LocalDate.now()),
				builder.like(root.get(Student_.description), "%17bang%"))));
就问你舒服不舒服?

在实际开发中,我们通常会根据用户选择,动态地生成查询语句,这时候需要:

  1. 声明一个predicate:
    Predicate predicate = builder.isTrue(builder.literal(true));
    这个predicate不能直接赋值为null(原因见第3条),所以
    • 要么有一个无论如何都需要的基本条件(比如第2条那样)
    • 要么就得如上生成一个1=1的恒等虚条件
  2. 把builder方法的返回值用predicate保存起来
    if (true) {
    	predicate = builder.and(builder.isNull(root.get(Student_.rest)));
    }
  3. 然后利用之前的predicate,进行or()或and()的组合。
    if (true) {
    	predicate = builder.or(predicate, builder.lessThan(root.get(Student_.enroll), LocalDate.now()));
    }
    if (true) {
    	predicate = builder.and(predicate, builder.like(root.get(Student_.description), "%17bang%"));
    }
    注意or()或and()里面的第一个参数predicate不能为null,所以一开始predicate不能直接设置为null(见第1条),否则有可能之前的分支没有走到,走到当前分支就直接报NullPointer异常。
  4. 最后:
    criteria.where(predicate);


orderBy:排序

两种方式,推荐第2种:
criteria.orderBy(new OrderImpl(root.get(Student_.age)));
criteria.orderBy(builder.desc(root.get(Student_.age)));

还可以多列排序:

criteria.orderBy(
		builder.desc(root.get(Student_.age)),
		builder.asc(root.get(Student_.id))
		);

排序之后取前三个呢?或者分页(跳过n个再取m个)呢?

TypedQuery<Student> query = em.createQuery(criteria)
		.setFirstResult(20)     //从第20个开始取
		.setMaxResults(10);    //最多取10个

log演示生成SQL:order by student0_.age desc, student0_.id asc limit 20, 10


投影

比如我只想取某几列的内容,或者包含额外的生成列。

Object[]

首先可以想到的是:查询的结果不能再是完整的entity了……

可以简单的使用Object[]指定查询结果类型:

CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);

于是相应的:CriteriaQuery<Object[]>、TypedQuery<Object[]>、List<Object[]>都要发生更改。

但是,Root<Student>不用更改,因为它指示的是查询的起点,我们仍然是基于Student开始查询的。

multiselect

然后需要multiselect()使用指定要查询的列/attribute:
criteria.multiselect(
	    root.get(Student_.name),
	    root.get(Student_.description)
	);

long演示生成的SQL:

select
	student0_.name as col_0_0_,
	student0_.description as col_1_0_ 
from
	Student student0_

就只查询了两列。

Tuple

Object[]就只能通过下标来定位,表现力不够。

所以我们还是推荐使用javax.persistence.Tuple作为查询结果元素容器:

CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);
//或者:
//CriteriaQuery<Tuple> criteria = builder.createTupleQuery();

它最大的好处是可以强类型的使用

for (Tuple tuple : students) {
	System.out.println(
			tuple.get(0) +  //可以用下标
			":" + 
			//但建议用更有表现力的强力型变量
			tuple.get(pDescription));
}
pDescription是为了重用,定义的变量:
Path<String> pDescription = root.get(Student_.description);


group:分组

比如:成绩(Score)按科目(name)分组

投影

首先要想到的,group的查询结果是典型的投影,因为拿到的结果应该是这个样子的:

科目
好成绩
Java
98
SQL
96
CSharp
96.5

不再是entity了,所以要改criteria。我们使用CriteriaQuery<Tuple>

groupBy

Path<String> pName = root.get(Score_.name);    //后面可以重用
//完成分组
criteria.groupBy(pName);

接下来就要使用multiselect,并指定分组过后的聚合运算:

//聚合运算函数count()
Expression<Long> eCount = builder.count(pName);
criteria.multiselect(pName, eCount);
除了count(),还可以有:
  • 最大:max() 或 greatest()
  • 最小:min() 或 least()
  • 平均:avg()
参数为Path,指向attribute,比如:
Path<Float> pPoint = root.get(Score_.point);

甚至可以用builder构建差值(最好最差之间):diff()

criteria.multiselect(pName, 
		builder.max(pPoint),
		builder.least(pPoint),
		builder.diff(builder.max(pPoint), builder.least(pPoint)) 
		);

演示log生成SQL:select ……, max(score0_.point)-min(score0_.point) as col_1_0_

having

可以直接在criteria后面加having()方法。但是为了重用,可以先抽出相关变量:
Path<String> pName = root.get(Score_.name);
Expression<Float> eMin = builder.min(root.get(Score_.point));
criteria.groupBy(pName);
criteria.multiselect(pName, eMin);
criteria.having(builder.greaterThan(eMin, 90f));

演示log生成SQL:having min(score0_.point)>90.0


作业

完成ORM介绍中第8题
学习笔记
源栈学历
大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 JDBC&Hibernate 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码