关注公众号: 锅外的大佬前端
每日推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!web
Spring MVC
和 Spring Data
都用他们的方式在简化应用程序开发作的很棒。可是,若是咱们将他们组合在一块儿呢?spring
在本教程中,咱们将查看 Spring Data
的Web支持 以及它的解析器如何减小样板文件并使咱们的controller
更有表现力。数据库
在此过程当中,咱们将查看Querydsl
及其与Spring Data
的集成。json
Spring Data
的Web
支持是一组在标准Spring MVC
平台上实现的与Web
相关的功能,目的是为controller
层添加额外的功能。数组
Spring Data web
支持的功能是围绕几个解析器类构建的。解析器简化了和 Spring Data repository
交互的controller
方法的实现,还使用额外的功能加强了他们。bash
这些功能包括从repository
层获取域对象,而不须要显式调用repository
实现,以及构建能够做为支持分页和排序的数据片断发送到客户端的controller
响应。app
此外,对于带有一个或多个请求参数到controller
方法的请求能够在内部被解析为 Querydsl
查询。dom
要了解咱们如何使用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 controller
,spring-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
注解。
如今,让咱们添加一个简单的 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
}
复制代码
为了简化代码,咱们的演示 Spring Boot
应用程序的功能将缩小为只从H2
内存数据库中获取一些 User 实体。
Spring Boot
能够轻松建立提供开箱即用的最小CRUD
功能的repository
实现。所以,让咱们定义一个和 User JPA
实体一块儿运行的简单的repository
接口:
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}
复制代码
除了继承 PagingAndSortingRepository
以外,UserRepository
接口的定义一点也不复杂。
这表示 Spring MVC
启用了对数据库记录自动分页和排序功能。
如今,咱们至少须要实现一个基础的RESTful controller
,它充当客户端和 repository
层间的中间层。
所以,让咱们建立一个 controller
类,它在其构造器中获取UserRepository
实例并添加一个方法来经过id
查找User
实体:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User findUserById(@PathVariable("id") User user) {
return user;
}
}
复制代码
最后,让咱们定义应用程序的主类,并使用一些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}
复制代码
目前,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
对象的查找。
Spring MVC
支持在controller
和repository
中使用 Pageable
类型。
简单来讲,Pageable
实例是一个保存分页信息的对象。所以,当咱们传递Pageable
参数给controller
方法,Spring MVC
使用 PageableHandlerMethodArgumentResolver
类将Pageable
实例解析为 PageRequest
对象,这是一个简单的Pageable
实现。
要理解 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
对象:
例如,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
}
复制代码
咱们能够看到,响应包括 first
、pageSize
、totalElements
和 totalPages
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"));
}
复制代码
在许多状况下,咱们须要自定义分页参数。完成此操做的最简单方法是使用 @PageableDefault
注解:
@GetMapping("/users")
public Page<User> findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
return userRepository.findAll(pageable);
}
复制代码
或者,咱们可使用 PageRequest
的 of()
静态工厂方法建立自定义PageRequest
对象并将其传递给repository
方法:
@GetMapping("/users")
public Page<User> findAllUsers() {
Pageable pageable = PageRequest.of(0, 5);
return userRepository.findAll(pageable);
}
复制代码
第一个参数是从 0 开始的页数,第二个是咱们但愿检索的页面大小。
在上面的例子中,咱们建立了一个User
实体的PageRequest
对象,从第一页(0)开始,页面有 5 个实体。
另外,咱们可使用page
和size
请求参数构建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
}
复制代码
分页是有效管理大量数据库记录的业界标准作法。可是,就其自己而言,若是咱们不能以某种特定方式对记录进行排序那将毫无用处。
为此,Spring MVC
提供了SortHandlerMethodArgumentResolver
类。该解析器自动从请求参数或@SortDefault
注解建立Sort
实例。
为了弄清楚 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
}
复制代码
或者,咱们可使用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
类中声明了的。
一样,咱们可使用 @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"));
}
复制代码
正如咱们介绍中提到的,Spring Data web
支持容许咱们在controller
方法中使用请求参数构建Querydsl
的Predicate
类型并构造Querydsl
查询。
为了简单起见,咱们将看到Spring MVC
如何将请求参数转换为Querydsl BooleanExpression
,而后传递给QuerydslPredicateExecutor
。
为此,咱们首先须要添加querydsl-apt
和 querydsl-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
支持如何运行的一个基础示例。但它实际上没有暴露出它的全部能力。
如今,假设咱们但愿获取匹配给定id
的User
实体。在这种状况下,咱们只须要在URL
中传递 id
请求参数:
http://localhost:8080/filteredusers?id=2
在这种状况下,咱们将获得这个结果:
[
{
"id": 2,
"name": "Robert"
}
]
复制代码
很明显,Querydsl web
支持是一个很是强大的功能,咱们可使用它来获取匹配给定条件的数据库记录。在全部状况下,整个过程归结为只调用具备不一样请求参数的单个 controller
方法。
在本教程中,咱们深刻查看了Spring web
支持的关键组件并学习了如何在演示Spring Boot
项目中使用它。
和往常同样,本教程中显示的全部示例均可以在 GitHub
上得到。
做者:Alejandro Ugarte
译者:Darren Luo 推荐关注公众号:锅外的大佬
推送国外优秀的技术翻译文章,励志帮助国内的开发者更好地成长!