Spring + MyBatis構(gòu)建REST簡單查詢語言(一)

構(gòu)建REST簡單查詢語言(一)

本系列文章起源于我在實際項目中遇到的問題,思路來源于網(wǎng)上的博客,可視為對該系列博客的思想繼承。如有興趣可訪問原博客。

1. 什么是REST查詢語言

如今REST接口大行其道,我們所構(gòu)建的http接口也都是REST。看過RESTful接口介紹的,會覺得這種接口十分簡單,語義也比較清楚。然而在實際的工作中,會發(fā)現(xiàn)網(wǎng)上的介紹類文章說的還是太淺了,我們所遇到的需求,要比教科書上復(fù)雜的多。

查詢是其中具有代表性的一種。

按照RESTful的定義來說,GET請求代表查詢資源,比如我們要查詢一個名為張三的用戶,那么RESTful風(fēng)格的表述可以是GET /users?name=張三 。是不是清晰簡單?

然而實際的情況要復(fù)雜的多。

在我們的系統(tǒng)中,可能有100個名叫張三的用戶,而實際我們只需要年齡在20至30之間,家住上海的那些。在這種情況下,簡單的用多個field進(jìn)行拼接難以滿足需求。因此,我們需要一個查詢表達(dá)式來面對復(fù)雜的查詢過濾需求。

2. 構(gòu)建一個簡單的查詢表達(dá)式

所以這個系列的目標(biāo)是構(gòu)建一個簡單的查詢表達(dá)式,便于映射到SQL查詢條件。

定義一個簡單的表達(dá)式,其要素為三個

  • 查詢字段 key
  • 操作 operation
  • 查詢參數(shù) value

如下表達(dá)式,name:張三,age>20,age<30,city:上海代表查詢系統(tǒng)中名叫張三,年齡在20至30之間,家住上海的用戶。可以發(fā)現(xiàn),這種方式更加靈活,并且有很好的擴(kuò)展性。

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及構(gòu)造方法
}

4. 使用ExampleBuilder映射REST查詢到數(shù)據(jù)庫查詢

接下來我們將面對最核心的問題:在數(shù)據(jù)持久化層該怎么構(gòu)建查詢。

其實也可以將問題轉(zhuǎn)換為:如何將自定義的查詢表達(dá)式表示為SQL語句。

下面的代碼就是在DAO層將查詢表達(dá)式構(gòu)建為MyBatis的Example,有賴于這種面向?qū)ο髽?gòu)建查詢條件的方式,能讓我們更方便的實現(xiàn)功能。

在我看的博客中,作者使用的是Spring JPA,并且使用CriteriaBuilder構(gòu)建查詢語句。而我的項目基本都是使用Mybatis,考慮到ORM層的切換成本,在本系列中我將使用MybatisExample構(gòu)建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類中,我們實際就是將表達(dá)式構(gòu)建為一個一個的Criteria,然后用and()方法將他們連接起來。

大家應(yīng)該發(fā)現(xiàn)了我們現(xiàn)在構(gòu)建的REST查詢語言只能表示AND關(guān)系。關(guān)于如何實現(xiàn)OR關(guān)系查詢我將在接下來的文章中介紹。

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構(gòu)建查詢語句的操作很簡單,主要依賴于SearchCriteria這個類。我們在其中將查詢條件轉(zhuǎn)換為三個屬性:

public class SearchCriteria {
    /**
     * 代表field name
     */
    private String key;
    /**
     * 代表執(zhí)行操作
     */
    private String operation;
    /**
     * 代表field value
     */
    private Object value;

Controller層,通過正則表達(dá)式來提取表達(dá)式并創(chuàng)建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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。