1. 概述
Spring MVC 和 Spring Data 各自都在简化应用开发方面表现优异。但如果将它们结合起来使用,会带来什么好处呢?
在本文中,我们将深入了解 Spring Data 的 Web 支持,并探讨它如何通过 resolver 减少样板代码,使我们的控制器更简洁、更具表达力。
同时,我们也会简单介绍 Querydsl 与 Spring Data 的集成方式。
2. 背景知识
Spring Data 的 Web 支持是一组构建在标准 Spring MVC 平台之上的 Web 相关功能,旨在为控制器层添加额外功能。
其核心机制围绕几个 resolver 类展开。这些解析器简化了控制器方法与 Spring Data 仓库的交互实现,并提供了额外功能。
主要功能包括:
- ✅ 从仓库层自动获取领域对象,无需显式调用仓库方法
- ✅ 构造支持分页和排序的响应数据
- ✅ 将请求参数自动转换为 Querydsl 查询
3. 示例 Spring Boot 项目
为了更好地理解 Spring Data Web 支持如何提升控制器的功能,我们创建一个简单的 Spring Boot 项目。
3.1. 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
默认启用了 Spring Data Web 支持,因此无需额外配置。
3.2. 领域类(User)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private final String name;
// 构造器、getter、toString 方法略
}
3.3. 仓库层(UserRepository)
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long> {}
✅
PagingAndSortingRepository
提供了开箱即用的分页和排序功能。
3.4. 控制器层(UserController)
@RestController
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/users/{id}")
public User findUserById(@PathVariable("id") User user) {
return user;
}
}
3.5. 启动类与初始化数据
@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. DomainClassConverter
UserController
中的 findUserById()
方法看似简单,但背后其实隐藏了 Spring Data Web 支持的巧妙机制。
@GetMapping("/users/{id}")
public User findUserById(@PathVariable("id") User user) {
return user;
}
我们并没有显式调用仓库查询用户,但 Spring MVC 会自动将路径变量 id
转换为 User
对象。
这是通过 DomainClassConverter
实现的。它会:
- ✅ 将
id
转换为对应实体类型 - ✅ 自动从仓库中获取对应对象
例如访问 /users/1
接口,返回结果如下:
{
"id": 1,
"name": "John"
}
5. PageableHandlerMethodArgumentResolver
Spring MVC 支持在控制器中使用 Pageable
类型,用于实现分页功能。
5.1. 在控制器中使用 Pageable
@GetMapping("/users")
public Page<User> findAllUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
Spring MVC 会自动将以下请求参数解析为 Pageable
:
page
:页码(从 0 开始)size
:每页数量sort
:排序字段(如?sort=name,asc
)
示例响应:
{
"content": [...],
"pageable": {
"pageSize": 5,
"pageNumber": 0
},
"totalPages": 1
}
5.2. 自定义分页参数
我们可以使用 @PageableDefault
或手动构造 PageRequest
:
@GetMapping("/users")
public Page<User> findAllUsers(@PageableDefault(value = 2, page = 0) Pageable pageable) {
return userRepository.findAll(pageable);
}
或:
Pageable pageable = PageRequest.of(0, 5);
也可以通过请求参数动态控制:
@GetMapping("/users")
public Page<User> findAllUsers(@RequestParam("page") int page,
@RequestParam("size") int size,
Pageable pageable) {
return userRepository.findAll(pageable);
}
6. SortHandlerMethodArgumentResolver
分页常与排序一起使用,Spring 提供了 SortHandlerMethodArgumentResolver
来处理排序逻辑。
6.1. 使用 sort
参数
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(@RequestParam("sort") String sort, Pageable pageable) {
return userRepository.findAll(pageable);
}
访问 /sortedusers?sort=name
将返回按 name
排序的用户列表。
6.2. 使用 Sort.by()
构造排序
Pageable pageable = PageRequest.of(0, 5, Sort.by("name"));
支持多个字段排序:
Sort.by("name", "id")
6.3. 使用 @SortDefault
@GetMapping("/sortedusers")
public Page<User> findAllUsersSortedByName(
@SortDefault(sort = "name", direction = Sort.Direction.ASC) Pageable pageable) {
return userRepository.findAll(pageable);
}
7. Querydsl Web 支持
Querydsl 是一种类型安全的查询构建方式。Spring Data Web 支持可以将请求参数自动转换为 Querydsl 的 Predicate
。
7.1. 添加依赖
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
7.2. 修改仓库接口
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long>,
QuerydslPredicateExecutor<User> {}
7.3. 控制器方法示例
@GetMapping("/filteredusers")
public Iterable<User> getUsersByQuerydslPredicate(
@QuerydslPredicate(root = User.class) Predicate predicate) {
return userRepository.findAll(predicate);
}
访问 /filteredusers?name=John
将返回所有名为 John
的用户。
测试示例:
@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"));
}
8. 总结
本文详细介绍了 Spring Data Web 支持的核心组件,包括:
组件 | 功能 |
---|---|
DomainClassConverter |
自动将路径变量转换为实体对象 |
PageableHandlerMethodArgumentResolver |
支持分页参数 |
SortHandlerMethodArgumentResolver |
支持排序参数 |
Querydsl 支持 | 支持基于请求参数的动态查询 |
这些功能显著减少了控制器中的样板代码,使开发更高效、代码更简洁。
所有示例代码可在 GitHub 上找到。