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 依赖项是相当标准的,但有一些例外,我们将在后面讨论:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

在本例中,我们包含了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 实体类,这样我们就可以有一个工作域模型来使用:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private final String name;
   
    // standard constructor / getters / toString

}

3.2. 存储库层

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

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

@Repository
public interface UserRepository extends PagingAndSortingRepository<User,Long> {}

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

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

3.3. 控制器层

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

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

@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public User findUserById(@PathVariable("id") User user) {
        return user;
    }
}

3.4. 运行应用程序

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

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }

    @Bean
    CommandLineRunner initialize(UserRepository userRepository) {
        return args -> {
            Stream.of("John","Robert","Nataly","Helen","Mary").forEach(name -> {
                User user = new User(name);
                userRepository.save(user);
            });
            userRepository.findAll().forEach(System.out::println);
        };
    }
}

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

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

4.域类转换器类

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

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

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

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

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

{
  "id":1,"name":"John"
}

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

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

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

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

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

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

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

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

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

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

@GetMapping("/users")
public Page<User> findAllUsers(Pageable pageable) {
    return userRepository.findAll(pageable);
}

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

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

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

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

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

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

{
  "content":[
    {
      "id":1,"name":"John"
    },{
      "id":2,"name":"Robert"
    },{
      "id":3,"name":"Nataly"
    },{
      "id":4,"name":"Helen"
    },{
      "id":5,"name":"Mary"
    }],"pageable":{
    "sort":{
      "sorted":false,"unsorted":true,"empty":true
    },"pageSize":5,"pageNumber":0,"offset":0,"unpaged":false,"paged":true
  },"last":true,"totalElements":5,"totalPages":1,"numberOfElements":5,"first":true,"size":5,"number":0,"sort":{
    "sorted":false,"empty":true
  },"empty":false
}

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

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

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

5.2. 自定义分页参数

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

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

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

@GetMapping("/users")
public Page<User> findAllUsers() {
    Pageable pageable = PageRequest.of(0,5);
    return userRepository.findAll(pageable);
}

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

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

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

@GetMapping("/users")
public Page<User> findAllUsers(@RequestParam("page") int page,@RequestParam("size") int size,Pageable pageable) {
    return userRepository.findAll(pageable);
}

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

{
  "content": [
    {
      "id": 1,"name": "John"
    },{
      "id": 2,"name": "Robert"
    }
  ],// continues with pageable metadata
  
}

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

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

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

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

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

@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@RequestParam("sort") String sort,Pageable pageable) {
    return userRepository.findAll(pageable);
}

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

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

{
  "content": [
    {
      "id": 4,"name": "Helen"
    },{
      "id": 1,{
      "id": 5,"name": "Mary"
    },{
      "id": 3,"name": "Nataly"
    },// continues with pageable metadata
  
}

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

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

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

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

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

6.3. 使用@SortDefault注释

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

@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@SortDefault(sort = "name",direction = Sort.Direction.ASC) Pageable pageable) {
    return userRepository.findAll(pageable);
}

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

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

7. 查询网页支持

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

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

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

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

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

@Repository
public interface UserRepository extends PagingAndSortingRepository<User,Long>,QuerydslPredicateExecutor<User> {
}

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

@GetMapping("/filteredusers")
public Iterable<User> getUsersByQuerydslPredicate(@QuerydslPredicate(root = User.class) 
  Predicate predicate) {
    return userRepository.findAll(predicate);
}

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

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

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

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

[
  {
    "id": 1,"name": "John"
  }
]

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

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

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

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

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

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

[
  {
    "id": 2,"name": "Robert"
  }
]

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

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

8. 结论

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

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

原文地址:https://blog.csdn.net/allway2

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原理介绍,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。传统事务中回滚点的使...
今天小编给大家分享的是一文解析spring中事务的传播机制,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区别,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。Spring Cloud Netfli...
本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。第一步:整合pom文件,在S...
本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。List 坑列表 = new ArrayList(2);...
这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇...
本篇内容主要讲解“Spring中的@Autowired和@Resource注解怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学...
今天小编给大家分享一下SpringSecurity怎么定义多个过滤器链的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
这篇文章主要介绍“Spring的@Conditional注解怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring的@Con...
这篇文章主要介绍了SpringCloudGateway的熔断限流怎么配置的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇SpringCloud&nb...
今天小编给大家分享一下怎么使用Spring解决循环依赖问题的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考
这篇文章主要介绍“Spring事务及传播机制的原理及应用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Sp...
这篇“SpringCloudAlibaba框架实例应用分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
本篇内容主要讲解“SpringBoot中怎么使用SpringMVC”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习...
这篇文章主要介绍“SpringMVC适配器模式作用范围是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringMVC
这篇“导入SpringCloud依赖失败如何解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要讲解了“SpringMVC核心DispatcherServlet处理流程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来
今天小编给大家分享一下SpringMVCHttpMessageConverter消息转换器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以...
这篇文章主要介绍“Spring框架实现依赖注入的原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring框架...
本篇内容介绍了“Spring单元测试控制Bean注入的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下