Spring Data JPA REST Query QueryDSL
案例概述
在本教程中,我们将研究使用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相关推荐
- Spring Data JPA 之 @Query 语法详解及其应用
5 Spring Data JPA 之 @Query 语法详解及其应用 5.1 快速体验 @Query 的方法 沿⽤我们之前的例⼦,新增⼀个 @Query 的⽅法: // 通过 query 注解根据 ...
- Spring Data Jpa使用@Query时 报错Validation failed for query for method public abstract
问题:在使用Spring Data Jpa作为持久化层,在使用@Query注解时出现以下错误: Validation failed for query for method public abstra ...
- java 注解 jpa_详解Spring Data JPA使用@Query注解(Using @Query)
经过几天的折腾,终于到了学习一个重量级的查询方式上,使用@query注解,使用注解有两种方式,一种是jpql的sql语言方式,一种是原生sql的语言,略有区别,后者我们更熟悉一些.话不多说,看代码. ...
- Spring Data JPA 实战
课程介绍 <Spring Data JPA 实战>内容是基于作者学习和工作中实践的总结和升华,有一句经典的话:"现在的开发人员是站在巨人的肩上,弯道超车".因现在框架越 ...
- 深入浅出学Spring Data JPA toPredicate Predicate[] p = new Predicate[list.size()]; query.where(cb.and 201
序言自工作以来,除了以前比较流量的hibernate,就是一直使用ORM 规范 JPA了.而这几天工作需要,研究了下JPA的标准查询,名为:JPA criteria查询.相比JPQL,其优势是类型安全 ...
- Spring Data JPA(官方文档翻译)
关于本书 介绍 关于这本指南 第一章 前言 第二章 新增及注意点 第三章 项目依赖 第四章 使用Spring Data Repositories 4.1 核心概念 4.2 查询方法 4.3 定义rep ...
- Spring Data JPA
1. 概述 Spring JPA通过为用户统一创建和销毁EntityManager,进行事务管理,简化JPA的配置等使用户的开发更加简便. Spring Data JPA是在Spring JPA ...
- spring data jpa从入门到精通_Spring Data JPA的简单入门
前言 spring data JPA是spring团队打造的sping生态全家桶的一部分,本身内核使用的是hibernate核心源码,用来作为了解java持久层框架基本构成的样本是再好不过的选择.最近 ...
- Spring Data JPA 教程(翻译)
写那些数据挖掘之类的博文 写的比较累了,现在翻译一下关于spring data jpa的文章,觉得轻松多了. 翻译正文: 你有木有注意到,使用Java持久化的API的数据访问代码包含了很多不必要的模式 ...
最新文章
- 慕课乐学python编程题_中国大学MOOC的APP(慕课)2020Python编程基础题目及答案
- 记一次接收微信公众平台推送消息的实例
- AJAX跨域请访问的问题
- c语言集合除去相同元素,使用C语言去掉字符串集合重复元素
- [PALAPALA] 无题 - 外来的和尚会念经
- 论文浅尝 | Rot-Pro:通过知识图谱嵌入中的投影建模关系的传递性
- 优化Nginx的处理性能
- 桌面排版神器:Affinity Publisher
- ckplayer6.8 php播放,ckplayer超酷网页视频播放器 6.8
- 2019年新个税计算方法
- 微信公众号文章采集思路
- JavaScript基础复习之数据类型,解读数据类型不为人知的一面
- TSNAdb:肿瘤新抗原数据库
- 百度振兴计划:中国版ChatGPT“狂飙”的机遇与挑战
- View 5应用之五:iPad与Android携带虚拟桌面
- sap客户信贷_通过SAP ABAP接口修改客户信贷主数据
- Python实用案例
- 专题 | 项目管理知识、方法论、工具NO.9:你应该知道的项目管理的五个过程组和九大知识领域
- 发票扫一扫,OCR识别功能
- 在虚拟机Ubuntu下安装java环境
热门文章
- 安全普及:关于网络远程控制和木马的几点误区
- 阿里巴巴2019实习生招聘正式启动!
- python manage.py runserver报错
- Play Framework 2.5.x 测试环境搭建
- c语言编程一对新出生的兔子,C语言兔子生兔子的问题(3中解法)
- Hadoop —— 漫画图解hdfs读、写、容错、副本机制
- 部署并安装Discuz论坛
- codeforces 1255 B. Fridge Lockers
- Codeforces Global Round 2 B. Alyona and a Narrow Fridge(二分)
- 第二次结对编程 微软学术搜索