1. 概述
本文将介绍 Spring Data 提供的 Querydsl Web 支持功能。
这是实现 REST 接口动态查询的一种非常优雅的方式,相比我们之前在《REST 查询语言系列》中讨论的其他方案(如 Specification、自定义解析器等),它更加类型安全且简洁。✅
如果你正在寻找一种简单粗暴但又足够强大的方式来支持 URL 参数动态查询,那这套组合值得你加入集合夹。
2. Maven 配置
首先,引入必要的依赖。核心是 spring-data-commons
和 Querydsl 相关组件:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
...
</dependencies>
⚠️ 注意:
Querydsl 的 Web 支持从 spring-data-commons 1.11 版本开始内置,无需额外集成。只要版本满足,开箱即用。
3. 用户仓库接口(UserRepository)
关键在于继承 QueryDslPredicateExecutor
和 QuerydslBinderCustomizer
,实现自定义绑定逻辑:
public interface UserRepository extends
JpaRepository<User, Long>, QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser> {
@Override
default public void customize(QuerydslBindings bindings, QUser root) {
bindings.bind(String.class).first(
(StringPath path, String value) -> path.containsIgnoreCase(value));
bindings.excluding(root.email);
}
}
重点说明:
- ✅
QueryDslPredicateExecutor
:提供基于Predicate
的查询能力 - ✅
QuerydslBinderCustomizer
:允许你定制请求参数到Predicate
的映射规则 - ✅
bindings.bind(String.class)
:对所有字符串字段统一设置为 忽略大小写的模糊匹配 - ❌
bindings.excluding(root.email)
:明确排除 email 字段,防止通过 URL 查询邮箱
💡 踩坑提醒:QUser
是 Querydsl 的元模型类,由注解处理器在编译期生成。确保已配置 querydsl-apt
插件,否则会报找不到此类。
4. 用户控制器(UserController)
控制器写法极其简洁,核心是 @QuerydslPredicate
注解:
@RequestMapping(method = RequestMethod.GET, value = "/users")
@ResponseBody
public Iterable<User> findAllByWebQuerydsl(
@QuerydslPredicate(root = User.class) Predicate predicate) {
return userRepository.findAll(predicate);
}
✅ 核心亮点:
- Spring MVC 会自动将 HTTP 请求参数解析为
Predicate
- 参数名对应实体字段名(支持嵌套属性,如
address.city
) - 不需要写任何解析逻辑,框架全帮你搞定
示例请求:
http://localhost:8080/users?firstName=john
返回结果示例:
[
{
"id": 1,
"firstName": "john",
"lastName": "doe",
"email": "john.doe@example.com",
"age": 11
}
]
支持的操作符(通过后缀控制):
操作符 | 示例参数 | 说明 |
---|---|---|
默认 | ?name=john |
根据字段类型决定行为(String 走 contains,数值走 equals) |
like |
?name.like=jo |
显式模糊匹配 |
gt / lt |
?age.gt=18 |
大于、小于 |
in |
?role.in=ADMIN,USER |
包含于列表 |
5. 集成测试(Live Test)
使用 TestRestTemplate 或 RestAssured 进行端到端测试,验证查询逻辑是否生效。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class UserLiveTest {
private ObjectMapper mapper = new ObjectMapper();
private User userJohn = new User("john", "doe", "john.doe@example.com");
private User userTom = new User("tom", "doe", "tom.doe@example.com");
private static boolean setupDataCreated = false;
@Before
public void setupData() throws JsonProcessingException {
if (!setupDataCreated) {
givenAuth().contentType(MediaType.APPLICATION_JSON_VALUE)
.body(mapper.writeValueAsString(userJohn))
.post("http://localhost:8080/users");
givenAuth().contentType(MediaType.APPLICATION_JSON_VALUE)
.body(mapper.writeValueAsString(userTom))
.post("http://localhost:8080/users");
setupDataCreated = true;
}
}
private RequestSpecification givenAuth() {
return RestAssured.given().auth().preemptive().basic("user1", "user1Pass");
}
}
测试用例 1:获取全部用户
@Test
public void whenGettingListOfUsers_thenCorrect() {
Response response = givenAuth().get("http://localhost:8080/users");
User[] result = response.as(User[].class);
assertEquals(result.length, 2);
}
测试用例 2:按 firstName 查询(精确字段名)
@Test
public void givenFirstName_whenGettingListOfUsers_thenCorrect() {
Response response = givenAuth().get("http://localhost:8080/users?firstName=john");
User[] result = response.as(User[].class);
assertEquals(result.length, 1);
assertEquals(result[0].getEmail(), userJohn.getEmail());
}
测试用例 3:按 lastName 模糊查询(partial match)
@Test
public void givenPartialLastName_whenGettingListOfUsers_thenCorrect() {
Response response = givenAuth().get("http://localhost:8080/users?lastName=do");
User[] result = response.as(User[].class);
assertEquals(result.length, 2);
}
测试用例 4:尝试通过 email 查询(应被忽略)
@Test
public void givenEmail_whenGettingListOfUsers_thenIgnored() {
Response response = givenAuth().get("http://localhost:8080/users?email=john");
User[] result = response.as(User[].class);
assertEquals(result.length, 2);
}
⚠️ 结果验证:
尽管 URL 中传了 email=john
,但由于我们在 customize()
方法中调用了 excluding(root.email)
,该条件被自动忽略,因此仍返回所有用户。
6. 总结
Spring Data Querydsl Web Support 是一个被低估但非常实用的功能,特别适合需要支持灵活查询的后台管理系统或内部服务。
✅ 优势总结:
- 类型安全:基于元模型(Q-class),避免字符串拼接错误
- 零解析代码:无需手动解析 request parameter
- 高度可定制:通过
QuerydslBinderCustomizer
精细控制每个字段的行为 - 天然防注入:基于 JPA Criteria,避免 SQL 注入风险
⚠️ 使用建议:
- 适用于中后台场景,对外 API 建议加一层白名单过滤
- 注意暴露字段的风险,敏感字段务必
excluding
- 生产环境建议结合分页(
Pageable
)一起使用
如果你还没用过这个特性,建议在下一个项目中尝试一下——一旦用上,就再也回不去了。