1. 概述
这是 系列教程 的第五篇文章,我们将借助一个非常实用的开源库 —— **rsql-parser**,来实现一个功能完整的 REST API 查询语言。
RSQL(RESTful Service Query Language)是 FIQL(Feed Item Query Language)的超集,语法简洁清晰,特别适合用于 REST 接口的过滤查询场景。✅ 它天生就和 REST 风格 API 很搭,用起来顺手,表达能力强。
相比自己造轮子设计查询语法,使用 RSQL 能避免很多“踩坑”问题,比如参数解析歧义、嵌套逻辑混乱等。
2. 准备工作
首先,在 pom.xml
中引入 rsql-parser 的 Maven 依赖:
<dependency>
<groupId>cz.jirutka.rsql</groupId>
<artifactId>rsql-parser</artifactId>
<version>2.1.0</version>
</dependency>
接着定义本文示例中使用的实体类 —— User
:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
private int age;
}
这个类很标准,后续所有查询都将基于它展开。
3. 解析请求
RSQL 的核心机制是将查询字符串解析成抽象语法树(AST),然后通过 访问者模式(Visitor Pattern) 遍历节点,生成对应的 JPA Specification
。
我们需要实现 RSQLVisitor
接口,创建自定义访问器 CustomRsqlVisitor
:
public class CustomRsqlVisitor<T> implements RSQLVisitor<Specification<T>, Void> {
private GenericRsqlSpecBuilder<T> builder;
public CustomRsqlVisitor() {
builder = new GenericRsqlSpecBuilder<T>();
}
@Override
public Specification<T> visit(AndNode node, Void param) {
return builder.createSpecification(node);
}
@Override
public Specification<T> visit(OrNode node, Void param) {
return builder.createSpecification(node);
}
@Override
public Specification<T> visit(ComparisonNode node, Void params) {
return builder.createSpecification(node);
}
}
接下来是构建 Specification
的核心类 —— GenericRsqlSpecBuilder
:
public class GenericRsqlSpecBuilder<T> {
public Specification<T> createSpecification(Node node) {
if (node instanceof LogicalNode) {
return createSpecification((LogicalNode) node);
}
if (node instanceof ComparisonNode) {
return createSpecification((ComparisonNode) node);
}
return null;
}
public Specification<T> createSpecification(LogicalNode logicalNode) {
List<Specification> specs = logicalNode.getChildren()
.stream()
.map(node -> createSpecification(node))
.filter(Objects::nonNull)
.collect(Collectors.toList());
Specification<T> result = specs.get(0);
if (logicalNode.getOperator() == LogicalOperator.AND) {
for (int i = 1; i < specs.size(); i++) {
result = Specification.where(result).and(specs.get(i));
}
} else if (logicalNode.getOperator() == LogicalOperator.OR) {
for (int i = 1; i < specs.size(); i++) {
result = Specification.where(result).or(specs.get(i));
}
}
return result;
}
public Specification<T> createSpecification(ComparisonNode comparisonNode) {
Specification<T> result = Specification.where(
new GenericRsqlSpecification<T>(
comparisonNode.getSelector(),
comparisonNode.getOperator(),
comparisonNode.getArguments()
)
);
return result;
}
}
关键点说明:
- ✅
LogicalNode
:表示逻辑操作(AND / OR),可包含多个子节点 - ✅
ComparisonNode
:表示单个比较条件,包含三个要素:- Selector:字段名(如
firstName
) - Operator:操作符(如
==
,=gt=
) - Arguments:参数值(如
["john"]
)
- Selector:字段名(如
例如,查询 firstName==john
的结构为:
组成部分 | 值 |
---|---|
Selector | firstName |
Operator | == |
Arguments | ["john"] |
4. 创建自定义 Specification
为了将 RSQL 条件映射到 JPA 查询,我们实现通用的 GenericRsqlSpecification<T>
类:
public class GenericRsqlSpecification<T> implements Specification<T> {
private String property;
private ComparisonOperator operator;
private List<String> arguments;
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Object> args = castArguments(root);
Object argument = args.get(0);
switch (RsqlSearchOperation.getSimpleOperator(operator)) {
case EQUAL: {
if (argument instanceof String) {
return builder.like(root.get(property), argument.toString().replace('*', '%'));
} else if (argument == null) {
return builder.isNull(root.get(property));
} else {
return builder.equal(root.get(property), argument);
}
}
case NOT_EQUAL: {
if (argument instanceof String) {
return builder.notLike(root.<String> get(property), argument.toString().replace('*', '%'));
} else if (argument == null) {
return builder.isNotNull(root.get(property));
} else {
return builder.notEqual(root.get(property), argument);
}
}
case GREATER_THAN: {
return builder.greaterThan(root.<String> get(property), argument.toString());
}
case GREATER_THAN_OR_EQUAL: {
return builder.greaterThanOrEqualTo(root.<String> get(property), argument.toString());
}
case LESS_THAN: {
return builder.lessThan(root.<String> get(property), argument.toString());
}
case LESS_THAN_OR_EQUAL: {
return builder.lessThanOrEqualTo(root.<String> get(property), argument.toString());
}
case IN:
return root.get(property).in(args);
case NOT_IN:
return builder.not(root.get(property).in(args));
}
return null;
}
private List<Object> castArguments(final Root<T> root) {
Class<? extends Object> type = root.get(property).getJavaType();
List<Object> args = arguments.stream().map(arg -> {
if (type.equals(Integer.class)) {
return Integer.parseInt(arg);
} else if (type.equals(Long.class)) {
return Long.parseLong(arg);
} else {
return arg;
}
}).collect(Collectors.toList());
return args;
}
// 构造函数、getter、setter 省略
}
⚠️ 注意:
- 使用泛型,不绑定具体实体,复用性强
- 支持类型自动转换(String → Integer/Long)
*
通配符被转换为 SQL 的%
,实现模糊匹配
再来看 RsqlSearchOperation
枚举,用于映射 RSQL 操作符:
public enum RsqlSearchOperation {
EQUAL(RSQLOperators.EQUAL),
NOT_EQUAL(RSQLOperators.NOT_EQUAL),
GREATER_THAN(RSQLOperators.GREATER_THAN),
GREATER_THAN_OR_EQUAL(RSQLOperators.GREATER_THAN_OR_EQUAL),
LESS_THAN(RSQLOperators.LESS_THAN),
LESS_THAN_OR_EQUAL(RSQLOperators.LESS_THAN_OR_EQUAL),
IN(RSQLOperators.IN),
NOT_IN(RSQLOperators.NOT_IN);
private ComparisonOperator operator;
private RsqlSearchOperation(ComparisonOperator operator) {
this.operator = operator;
}
public static RsqlSearchOperation getSimpleOperator(ComparisonOperator operator) {
for (RsqlSearchOperation operation : values()) {
if (operation.getOperator() == operator) {
return operation;
}
}
return null;
}
}
这个枚举起到了“翻译表”的作用,把 rsql-parser 的原生操作符转为我们熟悉的语义。
5. 测试查询功能
编写单元测试验证查询逻辑是否正确。先准备测试数据:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceConfig.class })
@Transactional
@TransactionConfiguration
public class RsqlTest {
@Autowired
private UserRepository repository;
private User userJohn;
private User userTom;
@Before
public void init() {
userJohn = new User();
userJohn.setFirstName("john");
userJohn.setLastName("doe");
userJohn.setEmail("john.doe@example.com");
userJohn.setAge(22);
repository.save(userJohn);
userTom = new User();
userTom.setFirstName("tom");
userTom.setLastName("doe");
userTom.setEmail("tom.doe@example.com");
userTom.setAge(26);
repository.save(userTom);
}
}
5.1 测试等于条件
查找 firstName == john
且 lastName == doe
的用户:
@Test
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
Node rootNode = new RSQLParser().parse("firstName==john;lastName==doe");
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
List<User> results = repository.findAll(spec);
assertThat(userJohn, isIn(results));
assertThat(userTom, not(isIn(results)));
}
✅ 使用 ;
表示 AND 逻辑。
5.2 测试否定条件
查找 firstName != john
的用户:
@Test
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
Node rootNode = new RSQLParser().parse("firstName!=john");
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
List<User> results = repository.findAll(spec);
assertThat(userTom, isIn(results));
assertThat(userJohn, not(isIn(results)));
}
5.3 测试大于条件
查找 age > 25
的用户:
@Test
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
Node rootNode = new RSQLParser().parse("age>25");
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
List<User> results = repository.findAll(spec);
assertThat(userTom, isIn(results));
assertThat(userJohn, not(isIn(results)));
}
5.4 测试模糊匹配
查找 firstName
以 jo
开头的用户:
@Test
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
Node rootNode = new RSQLParser().parse("firstName==jo*");
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
List<User> results = repository.findAll(spec);
assertThat(userJohn, isIn(results));
assertThat(userTom, not(isIn(results)));
}
✅ *
是通配符,会被转成 %
实现 LIKE
查询。
5.5 测试 IN 查询
查找 firstName
为 john
或 jack
的用户:
@Test
public void givenListOfFirstName_whenGettingListOfUsers_thenCorrect() {
Node rootNode = new RSQLParser().parse("firstName=in=(john,jack)");
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
List<User> results = repository.findAll(spec);
assertThat(userJohn, isIn(results));
assertThat(userTom, not(isIn(results)));
}
✅ in=
操作符非常实用,避免前端拼多个 or
条件。
6. UserController 接口实现
最后,把整个链路串起来,暴露一个支持 RSQL 查询的接口:
@RequestMapping(method = RequestMethod.GET, value = "/users")
@ResponseBody
public List<User> findAllByRsql(@RequestParam(value = "search") String search) {
Node rootNode = new RSQLParser().parse(search);
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
return repository.findAll(spec);
}
📌 示例请求 URL:
http://localhost:8082/spring-rest-query-language/auth/users?search=firstName==jo*;age<25
响应结果:
[{
"id": 1,
"firstName": "john",
"lastName": "doe",
"email": "john.doe@example.com",
"age": 24
}]
简单粗暴,一行参数搞定复杂查询。
7. 总结
本文展示了如何基于 rsql-parser 快速构建一个强大、灵活的 REST 查询语言,无需重复造轮子。
✅ 优势总结:
- ✅ 语法标准,学习成本低
- ✅ 支持 AND/OR、IN、LIKE、比较操作
- ✅ 与 Spring Data JPA 无缝集成
- ✅ 易于扩展自定义操作符
项目完整代码可在 GitHub 获取:
👉 https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-rest-query-language
Maven 项目,导入即用,适合中大型系统做通用查询模块的技术选型参考。