案例概述

在本教程中,我们将研究使用Spring Data JPA和Querydsl为REST API构建查询语言。

在本系列的前两篇文章中,我们使用JPA Criteria和Spring Data JPA规范构建了相同的搜索/过滤功能。

那么 - 为什么要使用查询语言?因为 - 对于任何复杂的API来说 - 通过非常简单的字段搜索/过滤资源是不够的。查询语言更灵活,允许您精确过滤所需的资源。

Querydsl配置

首先 - 让我们看看如何配置我们的项目以使用Querydsl。

我们需要将以下依赖项添加到pom.xml:

<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>4.1.4</version></dependency>
<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>4.1.4</version>
</dependency>

我们还需要配置APT - Annotation处理工具 - 插件如下:

<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.mysema.query.apt.jpa.JPAAnnotationProcessor</processor></configuration></execution></executions>
</plugin>

MyUser Entity

接下来 - 让我们看一下我们将在Search API中使用的“MyUser”实体:

@Entity
public class MyUser {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;private String firstName;private String lastName;private String email;private int age;
}

使用PathBuilder自定义Predicate

现在 - 让我们根据一些任意约束创建一个自定义Predicate。

我们在这里使用PathBuilder而不是自动生成的Q类型,因为我们需要动态创建路径以获得更抽象的用法:

public class MyUserPredicate {private SearchCriteria criteria;public BooleanExpression getPredicate() {PathBuilder<MyUser> entityPath = new PathBuilder<>(MyUser.class, "user");if (isNumeric(criteria.getValue().toString())) {NumberPath<Integer> path = entityPath.getNumber(criteria.getKey(), Integer.class);int value = Integer.parseInt(criteria.getValue().toString());switch (criteria.getOperation()) {case ":":return path.eq(value);case ">":return path.goe(value);case "<":return path.loe(value);}} else {StringPath path = entityPath.getString(criteria.getKey());if (criteria.getOperation().equalsIgnoreCase(":")) {return path.containsIgnoreCase(criteria.getValue().toString());}}return null;}
}

请注意Predicate的实现是通常如何处理多种类型的操作。这是因为查询语言根据定义是一种开放式语言,您可以使用任何支持的操作对任何字段进行过滤。

为了表示这种开放式过滤标准,我们使用了一个简单但非常灵活的实现 - SearchCriteria:

public class SearchCriteria {private String key;private String operation;private Object value;
}
  • key:用于保存字段名称 - 例如:firstName,age,…等。
  • operation:用于保持操作 - 例如:Equality,less,…等。
  • value:用于保存字段值 - 例如:john,25,…等。

MyUserRepository

现在 - 让我们来看看我们的MyUserRepository。

我们需要MyUserRepository来扩展QueryDslPredicateExecutor,以便我们以后可以使用Predicates来过滤搜索结果:

public interface MyUserRepository extends JpaRepository<MyUser, Long>, QueryDslPredicateExecutor<MyUser>, QuerydslBinderCustomizer<QMyUser> {@Overridedefault public void customize(QuerydslBindings bindings, QMyUser root) {bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);bindings.excluding(root.email);}
}

结合Predicates

接下来让我们看看组合Predicates在结果过滤中使用多个约束。

在以下示例中 - 我们使用构建器 - MyUserPredicatesBuilder - 来组合Predicates:

public class MyUserPredicatesBuilder {private List<SearchCriteria> params;public MyUserPredicatesBuilder() {params = new ArrayList<>();}public MyUserPredicatesBuilder with(String key, String operation, Object value) {params.add(new SearchCriteria(key, operation, value));return this;}public BooleanExpression build() {if (params.size() == 0) {return null;}List<BooleanExpression> predicates = new ArrayList<>();MyUserPredicate predicate;for (SearchCriteria param : params) {predicate = new MyUserPredicate(param);BooleanExpression exp = predicate.getPredicate();if (exp != null) {predicates.add(exp);}}BooleanExpression result = predicates.get(0);for (int i = 1; i < predicates.size(); i++) {result = result.and(predicates.get(i));}return result;}
}

测试搜索查询

接下来 - 让我们测试一下我们的Search API。

我们将首先使用少数用户初始化数据库 - 准备好这些数据并进行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceConfig.class })
@Transactional
@Rollback
public class JPAQuerydslIntegrationTest {@Autowiredprivate MyUserRepository repo;private MyUser userJohn;private MyUser userTom;@Beforepublic void init() {userJohn = new MyUser();userJohn.setFirstName("John");userJohn.setLastName("Doe");userJohn.setEmail("john@doe.com");userJohn.setAge(22);repo.save(userJohn);userTom = new MyUser();userTom.setFirstName("Tom");userTom.setLastName("Doe");userTom.setEmail("tom@doe.com");userTom.setAge(26);repo.save(userTom);}
}

接下来,让我们看看如何查找具有给定姓氏的用户

@Test
public void givenLast_whenGettingListOfUsers_thenCorrect() {MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "Doe");Iterable<MyUser> results = repo.findAll(builder.build());assertThat(results, containsInAnyOrder(userJohn, userTom));
}

现在,让我们看看如何找到具有名字和姓氏的用户

@Test
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "John").with("lastName", ":", "Doe");Iterable<MyUser> results = repo.findAll(builder.build());assertThat(results, contains(userJohn));assertThat(results, not(contains(userTom)));
}

接下来,让我们看看如何找到具有姓氏和最小年龄的用户

@Test
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "Doe").with("age", ">", "25");Iterable<MyUser> results = repo.findAll(builder.build());assertThat(results, contains(userTom));assertThat(results, not(contains(userJohn)));
}

接下来,让我们搜索实际不存在的用户:

@Test
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "Adam").with("lastName", ":", "Fox");Iterable<MyUser> results = repo.findAll(builder.build());assertThat(results, emptyIterable());
}

最后 - 让我们看看如何找到仅给出名字的一部分的MyUser - 如下例所示:

@Test
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "jo");Iterable<MyUser> results = repo.findAll(builder.build());assertThat(results, contains(userJohn));assertThat(results, not(contains(userTom)));
}

UserController

最后,让我们将所有内容放在一起并构建REST API。

我们定义了一个UserController,它定义了一个带有“search”参数的简单方法findAll()来传递查询字符串:

@Controller
public class UserController {@Autowiredprivate MyUserRepository myUserRepository;@RequestMapping(method = RequestMethod.GET, value = "/myusers")@ResponseBodypublic Iterable<MyUser> search(@RequestParam(value = "search") String search) {MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder();if (search != null) {Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),");Matcher matcher = pattern.matcher(search + ",");while (matcher.find()) {builder.with(matcher.group(1), matcher.group(2), matcher.group(3));}}BooleanExpression exp = builder.build();return myUserRepository.findAll(exp);}
}

这是一个快速测试URL示例:

http://localhost:8080/myusers?search=lastName:doe,age>25

回应:

[{"id":2,"firstName":"tom","lastName":"doe","email":"tom@doe.com","age":26
}]

案例结论

第三篇文章介绍了为REST API构建查询语言的第一步,充分利用了Querydsl库。

Spring Data JPA REST Query QueryDSL相关推荐

  1. Spring Data JPA 之 @Query 语法详解及其应用

    5 Spring Data JPA 之 @Query 语法详解及其应用 5.1 快速体验 @Query 的方法 沿⽤我们之前的例⼦,新增⼀个 @Query 的⽅法: // 通过 query 注解根据 ...

  2. Spring Data Jpa使用@Query时 报错Validation failed for query for method public abstract

    问题:在使用Spring Data Jpa作为持久化层,在使用@Query注解时出现以下错误: Validation failed for query for method public abstra ...

  3. java 注解 jpa_详解Spring Data JPA使用@Query注解(Using @Query)

    经过几天的折腾,终于到了学习一个重量级的查询方式上,使用@query注解,使用注解有两种方式,一种是jpql的sql语言方式,一种是原生sql的语言,略有区别,后者我们更熟悉一些.话不多说,看代码. ...

  4. Spring Data JPA 实战

    课程介绍 <Spring Data JPA 实战>内容是基于作者学习和工作中实践的总结和升华,有一句经典的话:"现在的开发人员是站在巨人的肩上,弯道超车".因现在框架越 ...

  5. 深入浅出学Spring Data JPA toPredicate Predicate[] p = new Predicate[list.size()]; query.where(cb.and 201

    序言自工作以来,除了以前比较流量的hibernate,就是一直使用ORM 规范 JPA了.而这几天工作需要,研究了下JPA的标准查询,名为:JPA criteria查询.相比JPQL,其优势是类型安全 ...

  6. Spring Data JPA(官方文档翻译)

    关于本书 介绍 关于这本指南 第一章 前言 第二章 新增及注意点 第三章 项目依赖 第四章 使用Spring Data Repositories 4.1 核心概念 4.2 查询方法 4.3 定义rep ...

  7. Spring Data JPA

    1.    概述 Spring JPA通过为用户统一创建和销毁EntityManager,进行事务管理,简化JPA的配置等使用户的开发更加简便. Spring Data JPA是在Spring JPA ...

  8. spring data jpa从入门到精通_Spring Data JPA的简单入门

    前言 spring data JPA是spring团队打造的sping生态全家桶的一部分,本身内核使用的是hibernate核心源码,用来作为了解java持久层框架基本构成的样本是再好不过的选择.最近 ...

  9. Spring Data JPA 教程(翻译)

    写那些数据挖掘之类的博文 写的比较累了,现在翻译一下关于spring data jpa的文章,觉得轻松多了. 翻译正文: 你有木有注意到,使用Java持久化的API的数据访问代码包含了很多不必要的模式 ...

最新文章

  1. 慕课乐学python编程题_中国大学MOOC的APP(慕课)2020Python编程基础题目及答案
  2. 记一次接收微信公众平台推送消息的实例
  3. AJAX跨域请访问的问题
  4. c语言集合除去相同元素,使用C语言去掉字符串集合重复元素
  5. [PALAPALA] 无题 - 外来的和尚会念经
  6. 论文浅尝 | Rot-Pro:通过知识图谱嵌入中的投影建模关系的传递性
  7. 优化Nginx的处理性能
  8. 桌面排版神器:Affinity Publisher
  9. ckplayer6.8 php播放,ckplayer超酷网页视频播放器 6.8
  10. 2019年新个税计算方法
  11. 微信公众号文章采集思路
  12. JavaScript基础复习之数据类型,解读数据类型不为人知的一面
  13. TSNAdb:肿瘤新抗原数据库
  14. 百度振兴计划:中国版ChatGPT“狂飙”的机遇与挑战
  15. View 5应用之五:iPad与Android携带虚拟桌面
  16. sap客户信贷_通过SAP ABAP接口修改客户信贷主数据
  17. Python实用案例
  18. 专题 | 项目管理知识、方法论、工具NO.9:你应该知道的项目管理的五个过程组和九大知识领域
  19. 发票扫一扫,OCR识别功能
  20. 在虚拟机Ubuntu下安装java环境

热门文章

  1. 安全普及:关于网络远程控制和木马的几点误区
  2. 阿里巴巴2019实习生招聘正式启动!
  3. python manage.py runserver报错
  4. Play Framework 2.5.x 测试环境搭建
  5. c语言编程一对新出生的兔子,C语言兔子生兔子的问题(3中解法)
  6. Hadoop —— 漫画图解hdfs读、写、容错、副本机制
  7. 部署并安装Discuz论坛
  8. codeforces 1255 B. Fridge Lockers
  9. Codeforces Global Round 2 B. Alyona and a Narrow Fridge(二分)
  10. 第二次结对编程 微软学术搜索