Spring + MyBatis构建REST简单查询语言(一)

2019-12-03  本文已影响0人  鱼头三

构建REST简单查询语言(一)

本系列文章起源于我在实际项目中遇到的问题,思路来源于网上的博客,可视为对该系列博客的思想继承。如有兴趣可访问原博客。

1. 什么是REST查询语言

如今REST接口大行其道,我们所构建的http接口也都是REST。看过RESTful接口介绍的,会觉得这种接口十分简单,语义也比较清楚。然而在实际的工作中,会发现网上的介绍类文章说的还是太浅了,我们所遇到的需求,要比教科书上复杂的多。

查询是其中具有代表性的一种。

按照RESTful的定义来说,GET请求代表查询资源,比如我们要查询一个名为张三的用户,那么RESTful风格的表述可以是GET /users?name=张三 。是不是清晰简单?

然而实际的情况要复杂的多。

在我们的系统中,可能有100个名叫张三的用户,而实际我们只需要年龄在20至30之间,家住上海的那些。在这种情况下,简单的用多个field进行拼接难以满足需求。因此,我们需要一个查询表达式来面对复杂的查询过滤需求。

2. 构建一个简单的查询表达式

所以这个系列的目标是构建一个简单的查询表达式,便于映射到SQL查询条件。

定义一个简单的表达式,其要素为三个

如下表达式,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层的切换成本,在本系列中我将使用MybatisExample构建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...

【参考资料】

REST Query Language with Spring and JPA Criteria

上一篇下一篇

猜你喜欢

热点阅读