構(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層的切換成本,在本系列中我將使用Mybatis的Example構(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...