1. 概述
在本教程中,我们将延续 Spring Data Querydsl Web 支持(第一部分) 的内容,进入第二阶段。✅
本次的重点是 关联实体(associated entities) 以及如何通过 HTTP 请求实现跨表查询。
项目结构仍基于 Maven,配置方式与第一部分一致。如果你还没看过前文,建议先回顾基础配置部分,避免踩坑。本文不再重复讲解环境搭建。
2. 实体设计
我们新增一个 Address
实体类,用于表示用户地址信息,并与 User
建立一对一关联。这里为了简化模型,使用 OneToOne
关系。
最终的两个实体如下:
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
private Address address;
// getters & setters
}
@Entity
public class Address {
@Id
@GeneratedValue
private Long id;
private String address;
private String country;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// getters & setters
}
⚠️ 注意:mappedBy = "user"
表示关系由 Address
端维护,避免双向映射时出现重复插入或更新问题。
3. Spring Data 仓库接口
接下来为每个实体创建对应的 Repository。关键在于启用 Querydsl 支持,以便支持动态谓词查询。
以 AddressRepository
为例,展示如何定制 Querydsl 绑定行为:
public interface AddressRepository extends JpaRepository<Address, Long>,
QuerydslPredicateExecutor<Address>, QuerydslBinderCustomizer<QAddress> {
@Override
default void customize(QuerydslBindings bindings, QAddress root) {
bindings.bind(String.class)
.first((SingleValueBinding<StringPath, String>) StringExpression::eq);
}
}
关键点说明:
- ✅
QuerydslPredicateExecutor
提供了基于Predicate
的查询能力 - ✅
QuerydslBinderCustomizer
允许我们自定义字段绑定逻辑 - ✅ 上述代码将所有
String
类型字段的默认匹配方式设为 精确等于(eq),而不是模糊匹配,避免误伤
💡 小技巧:如果不做这个定制,默认情况下 Querydsl 会对字符串使用
like
匹配,容易导致意外结果。显式声明更安全。
4. 查询用 RestController
在第一部分中,我们已经实现了对 User
的动态查询接口。现在只需复用相同模式,扩展到 Address
表即可。
新增一个接口用于查询地址数据:
@GetMapping(value = "/addresses", produces = MediaType.APPLICATION_JSON_VALUE)
public Iterable<Address> queryOverAddress(
@QuerydslPredicate(root = Address.class) Predicate predicate) {
BooleanBuilder builder = new BooleanBuilder();
return addressRepository.findAll(builder.and(predicate));
}
参数解析:
@QuerydslPredicate(root = Address.class)
:告诉 Spring Data Querydsl,HTTP 参数应映射到Address
的 Q-type 谓词BooleanBuilder
:用于组合多个条件,虽然此处只有一个 predicate,但保留它便于后续扩展
5. 集成测试验证
我们通过集成测试验证跨表查询是否生效。使用 MockMvc
模拟 HTTP 请求,测试能否通过地址国家字段过滤用户。
示例:查询所有居住在西班牙的用户
请求 URL:
/users?addresses.country=Spain
对应测试用例:
@Test
public void givenRequest_whenQueryUserFilteringByCountrySpain_thenGetJohn() throws Exception {
mockMvc.perform(get("/users?address.country=Spain"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].name", is("John")))
.andExpect(jsonPath("$[0].address.address", is("Fake Street 1")))
.andExpect(jsonPath("$[0].address.country", is("Spain")));
}
生成的 SQL 语句
Querydsl 会自动解析 HTTP 参数并生成等效 SQL:
select user0_.id as id1_1_,
user0_.name as name2_1_
from user user0_
cross join address address1_
where user0_.id=address1_.user_id
and address1_.country='Spain'
✅ 结果正确:通过 cross join
关联 user
和 address
表,并根据 country
条件过滤。
参数命名规则
注意 URL 中的参数名:
- ❌
/users?address.country=Spain
→ 错误(字段名不匹配) - ✅
/users?addresses.country=Spain
→ 正确(与User
实体中字段名一致)
⚠️ 踩坑提醒:Java 字段是
address
,但 Querydsl 生成的 Q-type 属性名是基于变量名的。如果字段名为address
,Q-type 中也是address
,但某些 IDE 或 Lombok 可能影响生成。建议打印 Q 类确认。
6. 总结
通过本篇实践,我们可以得出以下结论:
- ✅ Querydsl 提供了一种简单粗暴 yet 强大的方式,让前端可以通过 HTTP 参数直接构建复杂查询
- ✅ 支持跨表关联查询,无需手写 JPQL 或 Criteria API
- ✅ 结合
@QuerydslPredicate
与QuerydslBinderCustomizer
,可灵活控制字段绑定策略 - ✅ 对 REST API 来说,这种“零代码”实现动态查询的方式极大提升了开发效率
📌 延伸场景:
- 可进一步支持排序、分页(
Pageable
+Sort
) - 自定义绑定逻辑支持
in
、between
、嵌套对象等高级操作 - 安全性考虑:限制可查询字段,防止敏感信息泄露
完整示例代码已托管至 GitHub:
👉 https://github.com/example/tutorials/tree/master/persistence-modules/spring-data-rest-querydsl
项目基于 Maven 构建,导入即用,适合快速集成进现有 Spring Boot 项目。