构建REST简单查询语言(一)
本系列文章起源于我在实际项目中遇到的问题,思路来源于网上的博客,可视为对该系列博客的思想继承。如有兴趣可访问原博客。
1. 什么是REST查询语言
如今REST接口大行其道,我们所构建的http接口也都是REST。看过RESTful接口介绍的,会觉得这种接口十分简单,语义也比较清楚。然而在实际的工作中,会发现网上的介绍类文章说的还是太浅了,我们所遇到的需求,要比教科书上复杂的多。
查询是其中具有代表性的一种。
按照RESTful的定义来说,GET请求代表查询资源,比如我们要查询一个名为张三的用户,那么RESTful风格的表述可以是GET /users?name=张三
。是不是清晰简单?
然而实际的情况要复杂的多。
在我们的系统中,可能有100个名叫张三的用户,而实际我们只需要年龄在20至30之间,家住上海的那些。在这种情况下,简单的用多个field进行拼接难以满足需求。因此,我们需要一个查询表达式来面对复杂的查询过滤需求。
2. 构建一个简单的查询表达式
所以这个系列的目标是构建一个简单的查询表达式,便于映射到SQL查询条件。
定义一个简单的表达式,其要素为三个
- 查询字段 key
- 操作 operation
- 查询参数 value
如下表达式,name:张三,age>20,age<30,city:上海
代表查询系统中名叫张三,年龄在20至30之间,家住上海的用户。可以发现,这种方式更加灵活,并且有很好的扩展性。
3. 定义实体类
首先,我们来定义一个简单的User实体。
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private int age;
// getter,setter及构造方法
}
4. 使用ExampleBuilder映射REST查询到数据库查询
接下来我们将面对最核心的问题:在数据持久化层该怎么构建查询。
其实也可以将问题转换为:如何将自定义的查询表达式表示为SQL语句。
下面的代码就是在DAO层将查询表达式构建为MyBatis的Example,有赖于这种面向对象构建查询条件的方式,能让我们更方便的实现功能。
在我看的博客中,作者使用的是Spring JPA,并且使用CriteriaBuilder构建查询语句。而我的项目基本都是使用Mybatis,考虑到ORM层的切换成本,在本系列中我将使用Mybatis的Example构建SQL查询语句。
public class UserDAO implements IUserDAO {
private UserMapper userMapper;
@Override
public List<User> searchUser(List<SearchCriteria> params) {
Example example = new Example(User.class);
UserSearchQueryCriteriaConsumer searchConsumer = new UserSearchQueryCriteriaConsumer(example);
params.forEach(searchConsumer);
return userMapper.selectByExample(example);
}
在SearchQueryCriteriaConsumer
类中,我们实际就是将表达式构建为一个一个的Criteria
,然后用and()
方法将他们连接起来。
大家应该发现了我们现在构建的REST查询语言只能表示AND关系。关于如何实现OR关系查询我将在接下来的文章中介绍。
public class SearchQueryCriteriaConsumer implements Consumer<SearchCriteria> {
@NonNull
private Example example;
@Override
public void accept(SearchCriteria param) {
Example.Criteria criteria = example.createCriteria();
if (param.getOperation().equalsIgnoreCase(">")) {
criteria.andGreaterThan(param.getKey(), param.getValue());
} else if (param.getOperation().equalsIgnoreCase("<")) {
criteria.andLessThan(param.getKey(), param.getValue());
} else if (param.getOperation().equalsIgnoreCase(":")) {
criteria.andEqualTo(param.getKey(), param.getValue());
}
example.and(criteria);
}
}
使用Example构建查询语句的操作很简单,主要依赖于SearchCriteria
这个类。我们在其中将查询条件转换为三个属性:
public class SearchCriteria {
/**
* 代表field name
*/
private String key;
/**
* 代表执行操作
*/
private String operation;
/**
* 代表field value
*/
private Object value;
在Controller层,通过正则表达式来提取表达式并创建SearchCriteria
类。
public class UserController {
@Autowired
private IUserDAO userDAO;
@GetMapping
public @ResponseBody List<User> searchUser(@RequestParam("q") String query){
List<SearchCriteria> params = new ArrayList<>();
if (query != null) {
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
Matcher matcher = pattern.matcher(query + ",");
while (matcher.find()) {
params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3)));
}
}
userDAO.searchUser(params);
return userDAO.searchUser(params);
}
}
To Be Continue...
网友评论