• Spring Data Web支持


    1. 概述

    Spring MVCSpring Data各自在简化应用程序开发方面做得很好。但是,如果我们把它们放在一起呢?

    在本教程中,我们将了解Spring Data 的 Web 支持,以及它的解析器如何减少样板文件并使我们的控制器更具表现力。

    在此过程中,我们将了解Querydsl以及它与Spring Data的集成。

    2. 一点背景

    Spring Data的Web支持是在标准Spring MVC平台之上实现的一组与Web相关的功能,旨在为控制器层添加额外的功能

    Spring Data Web 支持的功能是围绕几个解析器类构建的。解析器简化了控制器方法的实现,这些控制器方法与Spring 数据存储库互操作,并通过附加功能丰富了它们。

    这些功能包括从存储库层获取域对象而无需显式调用存储库实现,以及构建控制器响应,这些响应可以作为支持分页和排序的数据段发送到客户端。

    此外,对采用一个或多个请求参数的控制器方法的请求可以在内部解析为Querydsl查询。

    3. 一个演示 Spring 启动项目

    为了了解如何使用 Spring Data Web 支持来改进控制器的功能,让我们创建一个基本的 Spring 启动项目。

    我们演示项目的 Maven 依赖项是相当标准的,但有一些例外,我们将在后面讨论:

    1. org.springframework.boot
    2. spring-boot-starter-data-jpa
    3. org.springframework.boot
    4. spring-boot-starter-web
    5. com.h2database
    6. h2
    7. runtime
    8. org.springframework.boot
    9. spring-boot-starter-test
    10. test

    在本例中,我们包含了spring-boot-starter-web,因为我们将使用它来创建 RESTful 控制器,spring-boot-starter-jpa 用于实现持久性层,以及 spring-boot-starter-test用于测试控制器 API。

    由于我们将使用H2作为底层数据库,因此我们也包括了com.h2database

    让我们记住,spring-boot-starter-web默认启用 Spring Data Web 支持。因此,我们不需要创建任何额外的@Configuration类来让它在我们的应用程序中工作。

    相反,对于非 Spring Boot 项目,我们需要定义一个@Configuration类,并用@EnableWebMvc@EnableSpringDataWebSupport注释对其进行注释。

    3.1. 域类

    现在,让我们向项目添加一个简单的UserJPA 实体类,这样我们就可以有一个工作域模型来使用:

    1. @Entity
    2. @Table(name = "users")
    3. public class User {
    4. @Id
    5. @GeneratedValue(strategy = GenerationType.AUTO)
    6. private long id;
    7. private final String name;
    8. // standard constructor / getters / toString
    9. }

    3.2. 存储库层

    为了保持代码简单,我们的演示 Spring 启动应用程序的功能将缩小到仅从 H2 内存数据库中获取一些用户实体。

    Spring 引导可以轻松创建存储库实现,这些存储库实现提供开箱即用的最小 CRUD 功能。因此,让我们定义一个简单的存储库接口,该接口适用于用户JPA 实体:

    1. @Repository
    2. public interface UserRepository extends PagingAndSortingRepository {}

    UserRepository接口的定义本身并没有什么复杂的,除了它扩展了PagingAndSortingRepository

    这表示Spring MVC对数据库记录启用自动分页和排序功能

    3.3. 控制器层

    现在,我们至少需要实现一个基本的 RESTful 控制器,它充当客户端和存储库层之间的中间层。

    因此,让我们创建一个控制器类,该类在其构造函数中采用UserRepository实例,并添加单个方法用于按id 查找用户实体:

    1. @RestController
    2. public class UserController {
    3. @GetMapping("/users/{id}")
    4. public User findUserById(@PathVariable("id") User user) {
    5. return user;
    6. }
    7. }

    3.4. 运行应用程序

    最后,让我们定义应用程序的主类,并使用几个User实体填充 H2 数据库:

    1. @SpringBootApplication
    2. public class Application {
    3. public static void main(String[] args) {
    4. SpringApplication.run(Application.class, args);
    5. }
    6. @Bean
    7. CommandLineRunner initialize(UserRepository userRepository) {
    8. return args -> {
    9. Stream.of("John", "Robert", "Nataly", "Helen", "Mary").forEach(name -> {
    10. User user = new User(name);
    11. userRepository.save(user);
    12. });
    13. userRepository.findAll().forEach(System.out::println);
    14. };
    15. }
    16. }

    现在,让我们运行该应用程序。正如预期的那样,我们看到在启动时打印到控制台的持久用户实体列表:

    1. User{id=1, name=John}
    2. User{id=2, name=Robert}
    3. User{id=3, name=Nataly}
    4. User{id=4, name=Helen}
    5. User{id=5, name=Mary}

    4.域类转换器类

    目前,UserController类只实现findUserById() 方法。

    乍一看,方法实现看起来相当简单。但它实际上在幕后封装了许多Spring Data Web支持功能。

    由于该方法将User实例作为参数,我们最终可能会认为我们需要在请求中显式传递域对象。但是,我们没有。

    Spring MVC 使用DomainClassConverter类将 id路径变量转换为域类的id类型,并使用它来从存储库层获取匹配的域对象。无需进一步查找。

    例如,对http://localhost:8080/users/1终结点的 GET HTTP 请求将返回以下结果:

    1. {
    2. "id":1,
    3. "name":"John"
    4. }

    因此,我们可以创建一个集成测试并检查findUserById() 方法的行为:

    1. @Test
    2. public void whenGetRequestToUsersEndPointWithIdPathVariable_thenCorrectResponse() throws Exception {
    3. mockMvc.perform(MockMvcRequestBuilders.get("/users/{id}", "1")
    4. .contentType(MediaType.APPLICATION_JSON_UTF8))
    5. .andExpect(MockMvcResultMatchers.status().isOk())
    6. .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"));
    7. }
    8. }

    或者,我们可以使用 REST API 测试工具(例如Postman)来测试该方法。

    DomainClassConverter的好处是,我们不需要在控制器方法中显式调用存储库实现。

    通过简单地指定idpath 变量以及可解析的域类实例,我们自动触发了域对象的查找

    5.可分页处理程序方法参数解析器

    Spring MVC 支持在控制器和存储库中使用可分页类型。

    简而言之,可分页实例是保存分页信息的对象。因此,当我们将可分页参数传递给控制器方法时,Spring MVC 使用PageableHandlerMethodArgumentResolver类将可分页实例解析为PageRequest对象,这是一个简单的可分页实现。

    5.1. 使用可分页作为控制器方法参数

    若要了解PageableHandlerMethodArgumentResolver类的工作原理,让我们向UserController类添加一个新方法:

    1. @GetMapping("/users")
    2. public Page findAllUsers(Pageable pageable) {
    3. return userRepository.findAll(pageable);
    4. }

    findUserById() 方法相反,这里我们需要调用存储库实现来获取数据库中持久化的所有用户JPA 实体。

    由于该方法采用可分页实例,因此它返回存储在Pageobject 中的整个实体集的子集。

    Page对象是对象列表的子列表,它公开了我们可用于检索有关分页结果的信息的几种方法,包括结果页的总数和要检索的页数。

    默认情况下,Spring MVC 使用PageableHandlerMethodArgumentResolver类来构造一个PageRequest对象,具有以下请求参数:

    • page:我们要检索的页面索引 – 参数索引为零,默认值为0
    • 大小:我们要检索的页数 - 默认值为20
    • sort:我们可以用来对结果进行排序的一个或多个属性,使用以下格式:property1,property2(,asc|desc) –例如,?sort=name&sort=email,asc

    例如,对http://localhost:8080/user终结点的 GET 请求将返回以下输出:

    1. {
    2. "content":[
    3. {
    4. "id":1,
    5. "name":"John"
    6. },
    7. {
    8. "id":2,
    9. "name":"Robert"
    10. },
    11. {
    12. "id":3,
    13. "name":"Nataly"
    14. },
    15. {
    16. "id":4,
    17. "name":"Helen"
    18. },
    19. {
    20. "id":5,
    21. "name":"Mary"
    22. }],
    23. "pageable":{
    24. "sort":{
    25. "sorted":false,
    26. "unsorted":true,
    27. "empty":true
    28. },
    29. "pageSize":5,
    30. "pageNumber":0,
    31. "offset":0,
    32. "unpaged":false,
    33. "paged":true
    34. },
    35. "last":true,
    36. "totalElements":5,
    37. "totalPages":1,
    38. "numberOfElements":5,
    39. "first":true,
    40. "size":5,
    41. "number":0,
    42. "sort":{
    43. "sorted":false,
    44. "unsorted":true,
    45. "empty":true
    46. },
    47. "empty":false
    48. }

    如我们所见,响应包括第一个pageSizetotalElementstotalPagesJSON元素。这非常有用,因为前端可以使用这些元素轻松创建分页机制。

    此外,我们可以使用集成测试来检查findAllUsers() 方法:

    1. @Test
    2. public void whenGetRequestToUsersEndPoint_thenCorrectResponse() throws Exception {
    3. mockMvc.perform(MockMvcRequestBuilders.get("/users")
    4. .contentType(MediaType.APPLICATION_JSON_UTF8))
    5. .andExpect(MockMvcResultMatchers.status().isOk())
    6. .andExpect(MockMvcResultMatchers.jsonPath("$['pageable']['paged']").value("true"));
    7. }

    5.2. 自定义分页参数

    在许多情况下,我们需要自定义分页参数。实现此目的的最简单方法是使用@PageableDefault注释:

    1. @GetMapping("/users")
    2. public Page findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
    3. return userRepository.findAll(pageable);
    4. }

    或者,我们可以使用 PageRequest 的of()static factory 方法来创建自定义PageRequest 对象并将其传递给存储库方法:

    1. @GetMapping("/users")
    2. public Page findAllUsers() {
    3. Pageable pageable = PageRequest.of(0, 5);
    4. return userRepository.findAll(pageable);
    5. }

    第一个参数是从零开始的页面索引,而第二个参数是我们要检索的页面的大小。

    在上面的示例中,我们创建了一个UserentityPageRequest对象,从第一页 (0) 开始,该页面有5个条目。

    此外,我们可以使用页面大小请求参数构建一个 PageRequest对象:

    1. @GetMapping("/users")
    2. public Page findAllUsers(@RequestParam("page") int page,
    3. @RequestParam("size") int size, Pageable pageable) {
    4. return userRepository.findAll(pageable);
    5. }

    使用此实现,对http://localhost:8080/users?page=0&size=2终结点的 GET 请求将返回User对象的第一页,结果页的大小将为 2:

    1. {
    2. "content": [
    3. {
    4. "id": 1,
    5. "name": "John"
    6. },
    7. {
    8. "id": 2,
    9. "name": "Robert"
    10. }
    11. ],
    12. // continues with pageable metadata
    13. }

    6.排序处理程序方法参数解析器

    分页是有效管理大量数据库记录的实际方法。但是,就其本身而言,如果我们不能以某种特定的方式对记录进行排序,那就毫无用处了。

    为此,Spring MVC 提供了SortHandlerMethodArgumentResolver类。解析程序根据请求参数或@SortDefault注释自动创建排序实例

    6.1. 使用排序控制器方法参数

    为了清楚地了解SortHandlerMethodArgumentResolver类的工作原理,让我们将findAllUsersSortedByName() 方法添加到控制器类中:

    1. @GetMapping("/sortedusers")
    2. public Page findAllUsersSortedByName(@RequestParam("sort") String sort, Pageable pageable) {
    3. return userRepository.findAll(pageable);
    4. }

    在这种情况下,将使用排序请求参数创建一个排序对象。

    因此,对http://localhost:8080/sortedusers?sort=name终结点的 GET 请求将返回一个 JSON 数组,其中User对象列表按name属性排序:

    1. {
    2. "content": [
    3. {
    4. "id": 4,
    5. "name": "Helen"
    6. },
    7. {
    8. "id": 1,
    9. "name": "John"
    10. },
    11. {
    12. "id": 5,
    13. "name": "Mary"
    14. },
    15. {
    16. "id": 3,
    17. "name": "Nataly"
    18. },
    19. {
    20. "id": 2,
    21. "name": "Robert"
    22. }
    23. ],
    24. // continues with pageable metadata
    25. }

    6.2. 使用Sort.by()静态工厂方法

    或者,我们可以使用Sort.by()static 工厂方法创建一个Sort对象,该方法采用一个非空、非空的String属性数组进行排序。

    在本例中,我们将仅按name属性对记录进行排序:

    1. @GetMapping("/sortedusers")
    2. public Page findAllUsersSortedByName() {
    3. Pageable pageable = PageRequest.of(0, 5, Sort.by("name"));
    4. return userRepository.findAll(pageable);
    5. }

    当然,我们可以使用多个属性,只要它们在域类中声明即可。

    6.3. 使用@SortDefault注释

    同样,我们可以使用 @SortDefault注释并获得相同的结果:

    1. @GetMapping("/sortedusers")
    2. public Page findAllUsersSortedByName(@SortDefault(sort = "name",
    3. direction = Sort.Direction.ASC) Pageable pageable) {
    4. return userRepository.findAll(pageable);
    5. }

    最后,让我们创建一个集成测试来检查方法的行为:

    1. @Test
    2. public void whenGetRequestToSorteredUsersEndPoint_thenCorrectResponse() throws Exception {
    3. mockMvc.perform(MockMvcRequestBuilders.get("/sortedusers")
    4. .contentType(MediaType.APPLICATION_JSON_UTF8))
    5. .andExpect(MockMvcResultMatchers.status().isOk())
    6. .andExpect(MockMvcResultMatchers.jsonPath("$['sort']['sorted']").value("true"));
    7. }

    7. 查询网页支持

    正如我们在介绍中提到的,Spring Data Web 支持允许我们在控制器方法中使用请求参数来构建 Querydsl 的谓词类型并构造Querydsl 查询。

    为了简单起见,我们将看到Spring MVC如何将请求参数转换为Querydsl BooleanExpression,而QuerydslBooleanExpression又传递给QuerydslPredicateExecutor

    为此,首先我们需要将querydsl-aptquerydsl-jpaMaven 依赖项添加到pom.xml文件中:

    1. com.querydsl
    2. querydsl-apt
    3. com.querydsl
    4. querydsl-jpa

    接下来,我们需要重构我们的UserRepository接口,该接口还必须扩展QuerydslPredicateExecutor接口:

    1. @Repository
    2. public interface UserRepository extends PagingAndSortingRepository,
    3. QuerydslPredicateExecutor {
    4. }

    最后,让我们将以下方法添加到UserController类中:

    1. @GetMapping("/filteredusers")
    2. public Iterable getUsersByQuerydslPredicate(@QuerydslPredicate(root = User.class)
    3. Predicate predicate) {
    4. return userRepository.findAll(predicate);
    5. }

    尽管方法实现看起来相当简单,但它实际上在表面之下公开了许多功能。

    假设我们要从数据库中获取与给定名称匹配的所有用户实体。我们可以通过调用该方法并在 URL 中指定名称请求参数来实现这一点:

    http://localhost:8080/filteredusers?name=John

    正如预期的那样,请求将返回以下结果:

    1. [
    2. {
    3. "id": 1,
    4. "name": "John"
    5. }
    6. ]

    和以前一样,我们可以使用集成测试来检查getUsersByQuerydslPredicate()方法:

    1. @Test
    2. public void whenGetRequestToFilteredUsersEndPoint_thenCorrectResponse() throws Exception {
    3. mockMvc.perform(MockMvcRequestBuilders.get("/filteredusers")
    4. .param("name", "John")
    5. .contentType(MediaType.APPLICATION_JSON_UTF8))
    6. .andExpect(MockMvcResultMatchers.status().isOk())
    7. .andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("John"));
    8. }

    这只是 Querydsl Web 支持工作原理的一个基本示例。但它实际上并没有揭示它的所有力量。

    现在,假设我们要获取与给定id 匹配的用户实体在这种情况下,我们只需要在 URL 中传递一个 id请求参数

    http://localhost:8080/filteredusers?id=2

    在这种情况下,我们将得到以下结果:

    1. [
    2. {
    3. "id": 2,
    4. "name": "Robert"
    5. }
    6. ]

    很明显,Querydsl Web 支持是一个非常强大的功能,我们可以用来获取与给定条件匹配的数据库记录。

    在所有情况下,整个过程都归结为仅调用具有不同请求参数的单个控制器方法

    8. 结论

    在本教程中,我们深入了解了 Spring Web 支持的关键组件,并学习如何在演示的 Spring 启动项目中使用它。

    像往常一样,本教程中显示的所有示例都可以在GitHub 上找到。

  • 相关阅读:
    SpringCloud Alibaba Sentinel 限流详解
    电脑快捷键
    函数式组件中实现Antd打开Modal后其Input框自动聚焦(focus)到文字的最后
    linux守护进程
    制作再生龙U盘启动镜像
    JSP SSH图书系统myeclipse开发sql数据库BS模式java编程mvc结构 详细设计
    关于高性能的MIMO技术的实现方法介绍
    SpringCloud02 --- Nacos安装指南
    如何从ChatGPT中获得最佳聊天对话效果
    问道管理:华为产业链股再度拉升,捷荣技术6连板,华力创通3日大涨近70%
  • 原文地址:https://blog.csdn.net/allway2/article/details/128145775