Spring Data Web支持

关注公众号: 锅外的大佬前端

每日推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!web

1.概述

Spring MVCSpring Data 都用他们的方式在简化应用程序开发作的很棒。可是,若是咱们将他们组合在一块儿呢?spring

在本教程中,咱们将查看 Spring Data的Web支持 以及它的解析器如何减小样板文件并使咱们的controller更有表现力。数据库

在此过程当中,咱们将查看Querydsl及其与Spring Data的集成。json

2.背景

Spring DataWeb支持是一组在标准Spring MVC平台上实现的与Web相关的功能,目的是为controller层添加额外的功能。数组

Spring Data web支持的功能是围绕几个解析器类构建的。解析器简化了和 Spring Data repository交互的controller方法的实现,还使用额外的功能加强了他们。bash

这些功能包括从repository层获取域对象,而不须要显式调用repository实现,以及构建能够做为支持分页和排序的数据片断发送到客户端的controller响应。app

此外,对于带有一个或多个请求参数到controller方法的请求能够在内部被解析为 Querydsl查询。dom

3.用Spring Boot项目演示

要了解咱们如何使用Spring Data web支持来改进咱们的controller的功能,让咱们建立一个基本的Spring Boot项目。spring-boot

咱们的演示项目的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 controllerspring-boot-starter-jpa用于实现持久层,以及spring-boot-starter-test用于测试controller API

因为咱们将采用H2 做为底层数据库,咱们也引入了com.h2database

让咱们记住,spring-boot-starter-web 默认启用了 Spring Data web支持。所以,咱们不须要建立任何额外的 @Configuration类来使其在咱们的应用程序中运行。

相反的,对于非Spring Boot项目,咱们须要定义一个@Configuration 类并使用 @EnableWebMvc@EnableSpringDataWebSupport 注解。

3.1.domain类

如今,让咱们添加一个简单的 User JPA 实体类到项目中,这样咱们能够有一个可用的域模型:

@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.repository层

为了简化代码,咱们的演示 Spring Boot 应用程序的功能将缩小为只从H2内存数据库中获取一些 User 实体。

Spring Boot能够轻松建立提供开箱即用的最小CRUD功能的repository 实现。所以,让咱们定义一个和 User JPA实体一块儿运行的简单的repository 接口:

@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}
复制代码

除了继承 PagingAndSortingRepository 以外,UserRepository 接口的定义一点也不复杂。

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

3.3.controller层

如今,咱们至少须要实现一个基础的RESTful controller,它充当客户端和 repository 层间的中间层。

所以,让咱们建立一个 controller 类,它在其构造器中获取UserRepository实例并添加一个方法来经过id查找User实体:

@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 实体:

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.DomainClassConverter类

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

初看之下,方法实现看起来至关简单。但它在幕后实际封装了许多Spring Data web支持的功能。因为该方法将User实例做为参数,咱们最终可能任务咱们须要在请求中显示传递domain 对象,但咱们没有。

Spring MVC 使用 DomainClassConverter 类转换id路径变量到domain类的 id类型并使用它从 repository层获取匹配的domain对象,无需进一步查找。例如,http://localhost:8080/users/1 端点(endpoint)的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 的好处是咱们不须要在controller 方法中显式的调用 repository实现,经过简单的指定 id路径变量以及可解析的 domain类实例,咱们已经触发了domain对象的查找。

5.PageableHandlerMethodArgumentResolver类

Spring MVC支持在controllerrepository 中使用 Pageable类型。

简单来讲,Pageable实例是一个保存分页信息的对象。所以,当咱们传递Pageable参数给controller方法,Spring MVC使用 PageableHandlerMethodArgumentResolver类将Pageable实例解析为 PageRequest对象,这是一个简单的Pageable实现。

5.1.使用Pageable做为controller方法参数

要理解 PageableHandlerMethodArgumentResolver 类如何运行。让咱们添加一个新方法到UserController 类:

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

findUserById() 方法做为对照,这里咱们须要调用 repository实现来或者数据库中持久化的全部User JPA实体。因为该方法使用了Pageable实例,所以它返回存储在Page<User> 对象中实体集的子集。

Page对象是咱们能够用于检索有关分页结果信息的对象列表的子列表对象,包括结果总页数和咱们正在检索的页数。

默认状况下,Spring MVC使用 PageableHandlerMethodArgumentResolver 类构造带有一下请求参数的 PageRequest对象:

  • page:咱们要检索的页数——该参数从 0 开始且它的默认值为 0
  • size:咱们要检索的页面的内容数量——默认值是 20
  • sort:咱们可使用一个或多个属性对结果排序,使用如下格式:property1,property2(,asc|desc) ——举个例子,?sort=name&sort=email,asc

例如,http://localhost:8080/users端点(endpoint)的 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,
    "unsorted":true,
    "empty":true
  },
  "empty":false
}
复制代码

咱们能够看到,响应包括 firstpageSizetotalElementstotalPages JSON 元素。这很是有用,由于前端可使用这些元素轻松建立分页机制。

另外,咱们可使用集成测试来检查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);
}
复制代码

或者,咱们可使用 PageRequestof() 静态工厂方法建立自定义PageRequest对象并将其传递给repository方法:

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

第一个参数是从 0 开始的页数,第二个是咱们但愿检索的页面大小。

在上面的例子中,咱们建立了一个User实体的PageRequest对象,从第一页(0)开始,页面有 5 个实体。

另外,咱们可使用pagesize请求参数构建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 端点(endpoint)的 GET请求将返回User对象的第一页,结果页的大小将为 2:

{
  "content": [
    {
      "id": 1,
      "name": "John"
    },
    {
      "id": 2,
      "name": "Robert"
    }
  ],

  // continues with pageable metadata

}
复制代码

6.SortHandlerMethodArgumentResolver类

分页是有效管理大量数据库记录的业界标准作法。可是,就其自己而言,若是咱们不能以某种特定方式对记录进行排序那将毫无用处。

为此,Spring MVC提供了SortHandlerMethodArgumentResolver类。该解析器自动从请求参数或@SortDefault注解建立Sort实例。

6.1.使用 sort controller 方法参数

为了弄清楚 SortHandlerMethodArgumentResolver类如何运做,让咱们添加 findAllUsersSortedByName() 方法到controller类:

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

在这种状况下,SortHandlerMethodArgumentResolver类将使用sort请求参数建立Sort对象。

所以,http://localhost:8080/sortedusers?sort=name端点(endpoint)的GET 请求将返回一个JSON 数组,按 name属性排序的User对象列表:

{
  "content": [
    {
      "id": 4,
      "name": "Helen"
    },
    {
      "id": 1,
      "name": "John"
    },
    {
      "id": 5,
      "name": "Mary"
    },
    {
      "id": 3,
      "name": "Nataly"
    },
    {
      "id": 2,
      "name": "Robert"
    }
  ],

  // continues with pageable metadata

}
复制代码

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

或者,咱们可使用Sort.by()静态工厂方法建立一个Sort对象,该方法接受一个非null,非空(empty)的用于排序的String属性的数组。

在这种状况下,咱们将只经过name属性对记录进行排序:

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

固然,咱们可使用多个属性,只要他们是在domain类中声明了的。

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. Querydsl Web支持

正如咱们介绍中提到的,Spring Data web支持容许咱们在controller方法中使用请求参数构建QuerydslPredicate类型并构造Querydsl查询。

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

为此,咱们首先须要添加querydsl-aptquerydsl-jpa Maven 依赖到 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);
}
复制代码

尽管方法实现看起来很是简单,但它实际上暴露了许多表面之下的功能。

假设咱们想要从数据库中获取匹配给定name的全部User实体。咱们能够经过调用方法并在URL中指定name 请求参数来实现:

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支持如何运行的一个基础示例。但它实际上没有暴露出它的全部能力。

如今,假设咱们但愿获取匹配给定idUser实体。在这种状况下,咱们只须要在URL中传递 id请求参数:

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

在这种状况下,咱们将获得这个结果:

[
  {
    "id": 2,
    "name": "Robert"
  }
]
复制代码

很明显,Querydsl web支持是一个很是强大的功能,咱们可使用它来获取匹配给定条件的数据库记录。在全部状况下,整个过程归结为只调用具备不一样请求参数的单个 controller 方法。

8.总结

在本教程中,咱们深刻查看了Spring web支持的关键组件并学习了如何在演示Spring Boot项目中使用它。

和往常同样,本教程中显示的全部示例均可以在 GitHub 上得到。

原文连接:www.baeldung.com/spring-data…

做者:Alejandro Ugarte

译者:Darren Luo 推荐关注公众号:锅外的大佬

推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!

相关文章
相关标签/搜索