之前在搞.net的時候,我們可以借助強(qiáng)大的ExpressionTree來解決,之前有一篇是微軟的EntityFramework表達(dá)式轉(zhuǎn)換:Linq to Entity經(jīng)驗:表達(dá)式轉(zhuǎn)換,是將一種表達(dá)式轉(zhuǎn)換成數(shù)據(jù)庫組件能夠識別的表達(dá)式,只不過那篇沒有涉及到View中的條件而已。頁面動態(tài)查詢的最簡單的方法就是解析View中特定的值來得到后臺組件能夠識別的查詢邏輯。
我們期待View中能夠這樣指定條件:
創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計、成都做網(wǎng)站、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)博愛,10余年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792
它的意思是查詢字段storeName,操作符是like,看起來并不難,但要解決這么幾個問題:
參數(shù)收集問題,表單域的值以什么樣的方式提交到后臺?
后臺接收參數(shù)類型是什么?
如何將表單域中的條件轉(zhuǎn)換成數(shù)據(jù)庫組件能夠識別的條件?
我們選擇的數(shù)據(jù)庫組件是mybatis+MySQL。個人感覺mybatis在處理動態(tài)查詢時比JPA在前期(技術(shù)學(xué)習(xí)前期,即水平還不太夠的時候)要簡單些,也可能是我對JPA的認(rèn)識還不夠,總感覺mybatis這種拼SQL的方式比較熟悉一些,也比較容易控制。當(dāng)然它們的定位本身就不同,這里不多討論?;趍ybatis我們采用了tk.mybatis這個開源的組件,它的功能非常豐富,分頁,通用mapper,代碼生成等大部分功能都已經(jīng)包含,大家有興趣可以去搜索。
注:下面的功能是我的同事完成,這里我做為學(xué)習(xí)的過程來分享下,可能也有理解不到位的地方,純屬個人學(xué)習(xí)理解。其中有部分功能未展示出來(比如權(quán)限過濾,and or這些分組查詢的支持等),只包含最基本的,每個項目的需求不同以及團(tuán)隊環(huán)境不同可以會有多種實現(xiàn)方式,選擇大家都能接受的就可以了。
我們再分別看下上面的三個問題怎么解決:
參數(shù)收集問題,表單域的值以什么樣的方式提交到后臺方法?
一般做頁面查詢時請求數(shù)據(jù)就兩種方式,get或者post。get一般是在采用了ajax這類技術(shù),post就復(fù)雜一些,分為兩種:一種也是采用ajax提交到后臺,一種是表單的提交。這里呢,由于我們采用了angularjs,所以很顯然只能采用ajax提交,如果查詢條件多,可采用ajax的post。由于上面貼的代碼片段顯示條件的name是動態(tài)的,所以我們不可能定義一個具體的后臺的業(yè)務(wù)Model對前臺的條件,比如有name,email,phone等等,所以我們采用將表單域整個序列化后的結(jié)果傳遞到后臺。
var requestData = $("#"+options.searchFormId+"").serialize(); var url = listUrl+"?"+requestData+"&pageNum="+$scopeLocal.pageRequest.pageNum; $.ajax({ type : "POST", url : url, dataType : 'json', async : false, beforeSend:options.beforeSend, error:options.error, success : function(data) { $scopeLocal.pageResponse = data; $scopeLocal.content=data.list; options.callback($scopeLocal,data); } });
2:參數(shù)類型是什么?
如果是表單提交方式,我們可以采用HttpServletRequest這個對象來接收所有表單域的值,但上一步我們采用的提交方式并非表單自身的提交,而是ajax的提交,ajax的請求,是不識別HttpServletRequest這個參數(shù)類型的,為此我們需要定義一個自定義的公共的對象來接收我們動態(tài)View中指定的條件,這里就有個我們的SearchModel,它包含如下內(nèi)容:
分頁信息,當(dāng)前頁,頁數(shù)據(jù)大小
搜索條件信息集合List
轉(zhuǎn)換為SQL的邏輯
SearchFilter:
public final class SearchFilter implements Serializable { private String propertyName; private Object value; private Operator operator; private String orGroup;
SearchModel:
public class SearchModel implements Serializable { private ListsearchFilters; private int pageNum = 1; private int pageSize = 10;
上面搜索條件的信息,我們需要從View中獲取,這里應(yīng)用HandlerMethodArgumentResolver來解決,它只有兩個方法:
判斷是否是支持的參數(shù)類型
boolean supportsParameter(MethodParameter parameter);
這個方法實現(xiàn)比較簡單,只需要判斷下當(dāng)前的參數(shù)類型是否是指定的類型即可:
@Override public boolean supportsParameter(MethodParameter parameter) { Class> parameterType = parameter.getParameterType(); return SearchModel.class.isAssignableFrom(parameterType); }
解析數(shù)據(jù)的詳細(xì)過程
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
這個方法是核心,之前有提到過,因為我們是ajax提交,在后臺的controller方法中不能包含HttpServletRequest request 這個參數(shù),但后臺要想取表單域的值從哪取呢?其實還是從這個參數(shù)中取,只不過我們需要換一種方法,可以從上面接口的的webRequest對象中獲取,有了這個對象也就意味著你得到了表單域的所有值了,后臺的事情就好辦了。
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
下面只需要一個轉(zhuǎn)換類將HttpServletRequest中的表單值填充到我們自定義的SearchModel中就可以了,這里需要一個專業(yè)處理轉(zhuǎn)換有類SearchFilterBuilder,首先將表單值轉(zhuǎn)換成一個集合,字符串類型的:
public static SearchFilterBuilder from(final HttpServletRequest request) { return new SearchFilterBuilder(request); } public ListbuildToStrings() { return buildToStrings(true); } public List buildToStrings(final boolean containsDataAuth) { List searchFilterStrings = Lists.newArrayList(); Map map = request.getParameterMap(); for (Map.Entry entry : map.entrySet()) { String strKey = entry.getKey(); for (String value : entry.getValue()) { if (!Strings.isNullOrEmpty(value) && !"none".equals(value) && strKey.startsWith(preWhere)) { String filedAndOp = strKey.substring(preWhere.length()); searchFilterStrings.add(String.format("%s=%s", filedAndOp, value)); } } } if (containsDataAuth) { // to do } return searchFilterStrings; }
基于上面得到的條件集合進(jìn)一步解析條件,由于我們前端View傳遞的條件是字符串的,所以這里應(yīng)用了一個專門的正則表達(dá)式的類DefaultSearchFilterStringProcessor去解析數(shù)據(jù)
public Listbuild() { List searchFilterStrings = buildToStrings(); List searchFilters = Lists.newArrayList(); searchFilters.addAll(searchFilterStrings.stream() .map(DefaultSearchFilterStringProcessor::from) .collect(Collectors.toList())); return searchFilters; }
由于字符串處理的邏輯與本文關(guān)聯(lián)并不大,這里就不貼相關(guān)代碼了,不會正則的用最笨的人肉解析字符串也是可以的,如前面說的都拿到Request對象了后面都好操作。
3:如果將表單域中的條件轉(zhuǎn)換成數(shù)據(jù)庫組件能夠識別的條件?
tk.mybatis或者是官方的mybatis-spring組件都支持動態(tài)條件,由于我這采用的是tk.mybatis,所以某些類都是tk.mybatis的,tk是進(jìn)一步的封裝,所以原理大體是相同的,之前有提到過操作mybtis有點像操作原生SQL,感覺就是一種拼SQL的過程,這里我們拼這個動態(tài)條件也是類似,簡單的話只需要一個靜態(tài)轉(zhuǎn)換方法就可以了,如果再深入一點可以想辦法做成自動識別并轉(zhuǎn)換,有能力的可研究。無非就是如下的轉(zhuǎn)換:
switch (op) { case EQ: this.criteria.andEqualTo(filed, value); break; case NOTEQ: this.criteria.andNotEqualTo(filed, value); break; case LE: this.criteria.andLessThanOrEqualTo(filed, value); break;
解決完上面這些,我們就可以直接這樣寫后臺代碼了:
controller:
@RequestMapping(value = "/getAllByPage") @ResponseBody public PageInfogetAllByPage(final SearchModel s1) { this.convertSearchModel(s1); return storeService.select(s1); }
service:有了example對象,分頁信息,排序字段,后面的就是tk.mybatis的基本功能了。
@Override public final PageInfoselect(final SearchModel searchModel) { Example example = ExampleBuilder.forClass(genericType).fromSearchFilters(searchModel.getSearchFilters()).build(); return select(example, searchModel.getPageNum(), searchModel.getPageSize(), searchModel.getOrderBy()); }
基于上面的內(nèi)容,針對查詢條件,我們可以在View中任意指定查語語句所包含的條件,后臺的controller以及service基本保持不變,應(yīng)付普通的管理界面查詢足夠了。