1. 概述

Spring MVCSpring 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 上找到。


原始标题:Spring Data Web Support

« 上一篇: Java 匿名类详解
» 下一篇: SQL 连接类型详解