数据访问层,所谓的CRUD是后端程序员的必修课程,Spring Data JPA 可以让我们来简化CRUD过程,本文由简入深,从JPA的基本用法,到各种高级用法。

Repository

Spring Data JPA 可以用来简化data access的实现,借助JPA我们可以快速的实现一些简单的查询,分页,排序不在话下。

public interface MovieRepository extends JpaRepository<Movie, Long> {
List<Movie> findByTitle(String title, Sort sort); Page<Movie> findByYear(Int year, Pageable pageable);
}

JPA会根据方法命名,通过JPA 查询生成器自动生成SQL,cool!

Criteria API

但是,简单并非万能,有时候也需要面对一些复杂的查询,不能享受JPA 查询生成器带来的便利。JPQ 提供了Criteria API

Criteria API 可以通过编程方式动态构建查询,强类型检查可以避免错误。核心原理就是构造一个Predicate

LocalDate today = new LocalDate();

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Movie> query = builder.createQuery(Movie.class);
Root<Movie> root = query.from(Movie.class); Predicate isComedy = builder.equal(root.get(Movie.genre), Genre.Comedy);
Predicate isReallyOld = builder.lessThan(root.get(Movie.createdAt), today.minusYears(25));
query.where(builder.and(isComedy, isReallyOld));
em.createQuery(query.select(root)).getResultList();

Predicate 可以很好的满足一些复杂的查询,但是他的问题在于不便于复用,因为你需要先构建CriteriaBuilder, CriteriaQuery, Root. 同时代码可读性也比较一般。

Specifications

能不能定义可复用的Predicate呢? JPA 提供Specification 接口来解决这个问题。

先来看这个接口定义:

public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

上文不是说需要先构建CriteriaBuilder, CriteriaQuery, Root吗,那么Specification接口就是给你提供这个三个参数,让你自己构建Predicate,想什么来什么。

我们用Specifications来改写代码,先定义Specification

public MovieSpecifications {
public static Specification<Movie> isComedy() {
return (root, query, cb) -> {
return cb.equal(root.get(Movie_.genre), Genre.Comedy);
};
}
public static Specification<Movie> isReallyOld() {
return (root, query, cb) -> {
return cb.lessThan(root.get(Movie_.createdAt), new LocalDate.now().minusYears(25));
};
}
}

然后改写MovieRepository ,为了让Repository可以运行Specification ,我们需要让其继承JpaSpecificationExecutor 接口。

public interface MovieRepository extends JpaRepository<Movie, Long>, JpaSpecificationExecutor<Movie> {
// query methods here
}

然后我们就可以愉快的使用定义好的Specification 了。

movieRepository.findAll(MovieSpecifications.isComedy());
movieRepository.findAll(MovieSpecifications.isReallyOld());

在这里,repository 的代理类,会自动准备好CriteriaBuilder, CriteriaQuery, Root,是不是很爽?

从面向对象编程来讲,MovieSpecifications并不是很优雅,你可以这样做:

public MovieComedySpecification implements Specification<Movie> {
@Override
public Predicate toPredicate(Root<Movie> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.get(Movie_.genre), Genre.Comedy);
}

联合Specifications

我们可以将多个predicates 合到一起使用,通过and,or来连接。

movieRepository.findAll(Specification.where(MovieSpecifications.isComedy())
.and(MovieSpecifications.isReallyOld()));

Specification 构造器

产品定义的业务逻辑,有时候会很复杂,比如我们需要根据条件动态拼接查询,我们可以定义一个SpecificationBuilder。

public enum SearchOperation {
EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE;
public static final String[] SIMPLE_OPERATION_SET =
{ ":", "!", ">", "<", "~" };
public static SearchOperation getSimpleOperation(final char input)
{
switch (input) {
case ':': return EQUALITY;
case '!': return NEGATION;
case '>': return GREATER_THAN;
case '<': return LESS_THAN;
case '~': return LIKE;
default: return null;
}
}
}
public class SearchCriteria {
private String key;
private Object value;
private SearchOperation operation;
} public final class MovieSpecificationsBuilder {
private final List<SearchCriteria> params; public MovieSpecificationsBuilder() {
params = new ArrayList<>();
}
public Specification<Movie> build() {
// convert each of SearchCriteria params to Specification and construct combined specification based on custom rules
}
public final MovieSpecificationsBuilder with(final SearchCriteria criteria) {
params.add(criteria);
return this;
}
}

使用方法:

final MovieSpecificationsBuilder msb = new MovieSpecificationsBuilder();
// add SearchCriteria by invoking with()
final Specification<Movie> spec = msb.build();
movieRepository.findAll(spec);

Querydsl

Querydsl, 动态查询语言,支持JPA。先引入:

<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency> <dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency> <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>

Querydsl会根据表结构,生成meta-model,需要引入APT插件

maven配置:

<project>
<build>
<plugins>
...
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
</project>

假设,我们有下面的Domain类:

@Entity
public class Customer { @Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private String firstname;
private String lastname; // … methods omitted
}

在这里生成,会根据表结构生成查询classes,比如QCustomer :

QCustomer customer = QCustomer.customer;
LocalDate today = new LocalDate();
BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));

对比Specifications,这里是BooleanExpression,基本上基于生成的代码就可以构造了,更方便快捷。

现在我们到JPA使用,JPA 接口需要继承QueryDslPredicateExecutor

public interface CustomerRepository extends JpaRepository<Customer>, QueryDslPredicateExecutor {
// Your query methods here
}

查询代码:

BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
customerRepository.findAll(customerHasBirthday.and(isLongTermCustomer));

同样的,Queydsl 还有一些类似直接写SQL的骚操作。

简单如:

QCustomer customer = QCustomer.customer;
Customer bob = queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob"))
.fetchOne();

多表查询:

QCustomer customer = QCustomer.customer;
QCompany company = QCompany.company;
query.from(customer, company);

多条件

queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob"), customer.lastName.eq("Wilson")); queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob").and(customer.lastName.eq("Wilson")));

使用JOIN

QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCat kitten = new QCat("kitten");
queryFactory.selectFrom(cat)
.innerJoin(cat.mate, mate)
.leftJoin(cat.kittens, kitten)
.fetch();

对应JPQL

inner join cat.mate as mate
left outer join cat.kittens as kitten

另外一个例子

queryFactory.selectFrom(cat)
.leftJoin(cat.kittens, kitten)
.on(kitten.bodyWeight.gt(10.0))
.fetch();

JPQL version

select cat from Cat as cat
left join cat.kittens as kitten
on kitten.bodyWeight > 10.0

Ordering

QCustomer customer = QCustomer.customer;
queryFactory.selectFrom(customer)
.orderBy(customer.lastName.asc(), customer.firstName.desc())
.fetch();

Grouping

queryFactory.select(customer.lastName).from(customer)
.groupBy(customer.lastName)
.fetch();

子查询

QDepartment department = QDepartment.department;
QDepartment d = new QDepartment("d");
queryFactory.selectFrom(department)
.where(department.size.eq(
JPAExpressions.select(d.size.max()).from(d)))
.fetch();

小结

本文简单介绍了JPA的Repository,以及面向动态查询的Querydsl和Specifications 的用法,使用JPA可以有效减少代码编写量,提升代码易读性和可维护性。

参考


作者:Jadepeng

出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

深入探索Spring Data JPA, 从Repository 到 Specifications 和 Querydsl的更多相关文章

  1. 扩展spring data jpa的repository

    在我们编写代码的过程中,spring data jpa为我们的持久层提供的极大的方便,但有时spring data jpa提供的repository并不能完全满足我们开发的需求,因此就需要进行扩展.s ...

  2. IntelliJ IDEA 2017版 spring-boot使用Spring Data JPA使用Repository<T, T>编程

    1.环境搭建pom.xml搭建 <?xml version="1.0" encoding="UTF-8"?> <project xmlns=& ...

  3. 【spring data jpa】repository中使用@Query注解使用hql查询,使用@Param引用参数,报错:For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on

    在spring boot中, repository中使用@Query注解使用hql查询,使用@Param引用参数 如题报错: For queries with named parameters you ...

  4. 【redis】5.spring boot项目中,直接在spring data jpa的Repository层使用redis +redis注解@Cacheable直接在Repository层使用,报错问题处理Null key returned for cache operation

    spring boot整合redis:http://www.cnblogs.com/sxdcgaq8080/p/8028970.html 首先,明确一下问题的场景 之前在spring boot整合re ...

  5. 快速搭建springmvc+spring data jpa工程

    一.前言 这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考. 二.创建maven工程 http://www.cnblo ...

  6. spring data jpa hibernate jpa 三者之间的关系

    JPA规范与ORM框架之间的关系是怎样的呢? JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服 ...

  7. 转:spring data jpa、 hibernate、 jpa 三者之间的关系

    原文链接:spring data jpa. hibernate. jpa 三者之间的关系 spring data jpa hibernate jpa 三者之间的关系 JPA规范与ORM框架之间的关系是 ...

  8. 学习Spring Data JPA

    简介 Spring Data 是spring的一个子项目,在官网上是这样解释的: Spring Data 是为数据访问提供一种熟悉且一致的基于Spring的编程模型,同时仍然保留底层数据存储的特​​殊 ...

  9. spring data jpa、 hibernate、 jpa 三者之间的关系

    http://www.cnblogs.com/xiaoheike/p/5150553.html JPA规范与ORM框架之间的关系是怎样的呢? JPA规范本质上就是一种ORM规范,注意不是ORM框架-- ...

随机推荐

  1. Java 客户端操作 FastDFS 实现文件上传下载替换删除

    FastDFS 的作者余庆先生已经为我们开发好了 Java 对应的 SDK.这里需要解释一下:作者余庆并没有及时更新最新的 Java SDK 至 Maven 中央仓库,目前中央仓库最新版仍旧是 1.2 ...

  2. vue去掉地址栏#号

    mode:'history' 将这代码放入router.js里面

  3. ubuntu20 使用命令安装 mysql

    命令安装 mysql sudo apt-get update sudo apt-get install -y mysql-server mysql-client 查看 mysql 安装情况 servi ...

  4. MeteoInfoLab脚本示例:MODIS AOD

    MODIS的气溶胶光学厚度(AOD)产品应用很广,数据可以在Giovanni上下载:http://disc.sci.gsfc.nasa.gov/giovanni/overview/index.html ...

  5. java读取中文乱码解决方法

    Java读取文本文件(例如csv文件.txt文件等),遇到中文就变成乱码.读取代码如下: List<String> lines=new ArrayList<String>(); ...

  6. Django的安装和项目的启动

    一.安装(安装最新LTS版): 1.命令行安装 pip install django==1.11.18 -i 源 2.pycharm 安装    二.创建项目 1.命令行创建 下面的命令创建了一个名为 ...

  7. 利用github创建个人网站

    先建个仓库,仓库名为: 用户名.github.io 进入仓库,删除README.md文件 新建个文件,文件名为: index.html,内容根据自己要求编写 打开网站,地址:https://用户名.g ...

  8. Android Jetpack从入门到精通(深度好文,值得收藏)

    前言 即学即用Android Jetpack系列Blog的目的是通过学习Android Jetpack完成一个简单的Demo,本文是即学即用Android Jetpack系列Blog的第一篇. 记得去 ...

  9. spring boot:使接口返回统一的RESTful格式数据(spring boot 2.3.1)

    一,为什么要使用REST? 1,什么是REST? REST是软件架构的规范体系,它把资源的状态用URL进行资源定位, 以HTTP动作(GET/POST/DELETE/PUT)描述操作 2,REST的优 ...

  10. centos8平台基于iftop监控网络流量

    一,iftop的作用: 基于ip统计外部机器与本机之间的网络流量, 可以方便的查看各客户端是否有非正常的到本机的访问 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnbl ...