标签:find code 结果 继承 理解 能力 检查 应用程序开发 元素
Spring Data Web 支持点击左上角,关注:“锅外的大佬”
专注分享国外最新技术内容
帮助每位开发者更优秀地成长
SpringMVC 和 SpringData 都用他们的方式在简化应用程序开发做的很棒。但是,如果我们将他们组合在一起呢?
在本教程中,我们将查看 SpringData的Web支持 以及它的解析器如何减少样板文件并使我们的 controller更有表现力。
在此过程中,我们将查看 Querydsl及其与 SpringData的集成。
SpringData的 Web支持是一组在标准 SpringMVC平台上实现的与 Web相关的功能,目的是为 controller层添加额外的功能。
SpringDataweb支持的功能是围绕几个解析器类构建的。解析器简化了和 SpringDatarepository交互的 controller方法的实现,还使用额外的功能增强了他们。
这些功能包括从 repository层获取域对象,而不需要显式调用 repository实现,以及构建可以作为支持分页和排序的数据片段发送到客户端的 controller响应。
此外,对于带有一个或多个请求参数到 controller方法的请求可以在内部被解析为 Querydsl查询。
要了解我们如何使用 SpringDataweb支持来改进我们的 controller的功能,让我们创建一个基本的 SpringBoot项目。
我们的演示项目的 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,我们将用他创建 RESTfulcontroller, spring-boot-starter-jpa用于实现持久层,以及 spring-boot-starter-test用于测试 controller API。
由于我们将采用 H2 作为底层数据库,我们也引入了 com.h2database。
让我们记住, spring-boot-starter-web 默认启用了 SpringDataweb支持。因此,我们不需要创建任何额外的 @Configuration类来使其在我们的应用程序中运行。
相反的,对于非 SpringBoot项目,我们需要定义一个 @Configuration 类并使用 @EnableWebMvc 和 @EnableSpringDataWebSupport 注解。
现在,让我们添加一个简单的 UserJPA 实体类到项目中,这样我们可以有一个可用的域模型:
@Entity
@Table
(
name
=
"users"
)
public
class
User
{
@Id
@GeneratedValue
(
strategy
=
GenerationType
.
AUTO
)
private
long
id
;
private
final
String
name
;
// standard constructor / getters / toString
}
为了简化代码,我们的演示 SpringBoot 应用程序的功能将缩小为只从 H2内存数据库中获取一些 User 实体。
SpringBoot可以轻松创建提供开箱即用的最小 CRUD功能的 repository 实现。因此,让我们定义一个和 UserJPA实体一起运行的简单的 repository 接口:
@Repository
public
interface
UserRepository
extends
PagingAndSortingRepository
<
User
,
Long
>
{}
除了继承 PagingAndSortingRepository 之外, UserRepository 接口的定义一点也不复杂。
这表示 SpringMVC启用了对数据库记录自动分页和排序功能。
现在,我们至少需要实现一个基础的 RESTfulcontroller,它充当客户端和 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()方法。
初看之下,方法实现看起来相当简单。但它在幕后实际封装了许多 SpringDataweb支持的功能。由于该方法将 User实例作为参数,我们最终可能任务我们需要在请求中显示传递 domain对象,但我们没有。
SpringMVC 使用 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对象的查找。
SpringMVC支持在 controller 和 repository 中使用 Pageable类型。
简单来说, Pageable实例是一个保存分页信息的对象。因此,当我们传递 Pageable参数给 controller方法, SpringMVC使用 PageableHandlerMethodArgumentResolver类将 Pageable实例解析为 PageRequest对象,这是一个简单的 Pageable实现。
要理解 PageableHandlerMethodArgumentResolver 类如何运行。让我们添加一个新方法到 UserController 类:
@GetMapping
(
"/users"
)
public
Page
<
User
>
findAllUsers
(
Pageable
pageable
)
{
return
userRepository
.
findAll
(
pageable
);
}
和 findUserById() 方法作为对照,这里我们需要调用 repository实现来或者数据库中持久化的所有 UserJPA实体。由于该方法使用了 Pageable实例,因此它返回存储在 Page<User>对象中实体集的子集。
Page对象是我们可以用于检索有关分页结果信息的对象列表的子列表对象,包括结果总页数和我们正在检索的页数。
默认情况下, SpringMVC使用 PageableHandlerMethodArgumentResolver 类构造带有一下请求参数的 PageRequest对象:
{
"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
}
分页是有效管理大量数据库记录的业界标准做法。但是,就其本身而言,如果我们不能以某种特定方式对记录进行排序那将毫无用处。
为此, SpringMVC提供了 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"
));
}
正如我们介绍中提到的, SpringDataweb支持允许我们在 controller方法中使用请求参数构建 Querydsl的 Predicate类型并构造 Querydsl查询。
为了简单起见,我们将看到 SpringMVC 如何将请求参数转换为 QuerydslBooleanExpression,然后传递给 QuerydslPredicateExecutor。
为此,我们首先需要添加 querydsl-apt 和 querydsl-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
);
}
尽管方法实现看起来非常简单,但它实际上暴露了许多表面之下的功能。
假设我们想要从数据库中获取匹配给定 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"
));
}
这只是 Querydslweb支持如何运行的一个基础示例。但它实际上没有暴露出它的所有能力。
现在,假设我们希望获取匹配给定 id的 User实体。在这种情况下,我们只需要在 URL中传递 id请求参数:
http://localhost:8080/filteredusers?id=2
在这种情况下,我们将得到这个结果:
[
{
"id"
:
2
,
"name"
:
"Robert"
}
]
很明显, Querydslweb支持是一个非常强大的功能,我们可以使用它来获取匹配给定条件的数据库记录。在所有情况下,整个过程归结为只调用具有不同请求参数的单个 controller 方法。
在本教程中,我们深入查看了 Springweb支持的关键组件并学习了如何在演示 SpringBoot项目中使用它。
和往常一样,本教程中显示的所有示例都可以在 GitHub 上获得。
原文链接:https://www.baeldung.com/spring-data-web-support
作者:Alejandro Ugarte
译者:Darren Luo
推荐阅读:Spring Data JPA投影查询
上篇好文:Lombok 简介
标签:find code 结果 继承 理解 能力 检查 应用程序开发 元素
原文地址:https://blog.51cto.com/14901350/2524991