Dynamic Condition search on the list page.
How do I create a list page? I mentioned the dynamic condition search on the list page. The main purpose is to dynamically specify conditions in the View, and the Data Query logic at the backend is as unchanged as possible. I was working on it. net, we can use the powerful ExpressionTree to solve the problem. In the previous article, Microsoft's EntityFramework expression conversion: Linq to Entity experience: expression conversion, is to convert an expression into an expression that can be recognized by the database component, but that article does not involve the conditions in the View. The simplest method for dynamic page query is to parse the specific values in the View to obtain the query logic that can be recognized by the background component.
We expect the View to specify the condition as follows:
<input type="text" name="WHERE.storeName.LIKE" class="form-control" style="width: 180px;" " />
It means to query the storeName field. The operator is like, which does not seem to be difficult, but it has to solve the following problems:
The database component we selected is mybatis + mysql. I personally feel that mybatis is easier to process dynamic queries than JPA in the early stage (in the early stage of technical learning, that is, when the level is not enough). It may also be because I do not know enough about JPA, I always feel that mybatis is a more familiar and easy to control. Of course, their positioning is different. We will not discuss them here. Based on mybatis, we use the open-source component tk. mybatis, which has a wide range of functions, including paging, General er, code generation, and other functions. If you are interested, you can search for them.
Note: The following functions are completed by my colleagues. Here, I will share with you the learning process, and I may not be able to understand them. They are purely personal learning and understanding. Some of these functions are not displayed (for example, permission filtering, and or grouping query support), and only include the most basic, different project requirements and team environments can be implemented in multiple ways.
Let's take a look at how to solve the above three problems:
Generally, two request data methods are available for page query: get or post. Get is generally based on ajax technology, and post is more complicated, divided into two types: one is to use ajax to submit to the background, and the other is form submission. Here, because angularjs is used, it is clear that ajax can only be used for submission. If there are many query conditions, ajax post can be used. Because the name of the display condition of the code snippet posted above is dynamic, it is impossible to define a specific background business Model for foreground conditions, such as name, email, and phone, therefore, the serialized results of form fields are passed to the background.
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: What is the parameter type?
For the form submission method, we can use the HttpServletRequest object to receive the values of all form fields. However, in the previous step, the submission method is not the submission of the form itself, but the ajax submission, ajax requests do not recognize the parameter type HttpServletRequest. Therefore, we need to define a custom public object to receive the conditions specified in our dynamic View, here we have a SearchModel, which contains the following content:
- Page Information, current page, page data size
- List <SearchFilter>, a set of Custom Search conditions, including field names, operators, and values, is the focus of this article.
- Logic for converting to 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 List<SearchFilter> searchFilters; private int pageNum = 1; private int pageSize = 10;
We need to obtain the information of the above search condition from the View. Here HandlerMethodArgumentResolver is used to solve the problem. There are only two methods:
- Determine whether the parameter type is supported
boolean supportsParameter(MethodParameter parameter);
This method is easy to implement. You only need to determine whether the current parameter type is of the specified type:
@Override public boolean supportsParameter(MethodParameter parameter) { Class<?> parameterType = parameter.getParameterType(); return SearchModel.class.isAssignableFrom(parameterType); }
- Detailed data parsing process
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
This method is the core, as we mentioned before, because we submit ajax, the controller Method in the background cannot contain the parameter HttpServletRequest request, but where does the background need to obtain the value of the form field? In fact, it is still obtained from this parameter, but we need to use another method, which can be obtained from the webRequest object of the interface above, with this object, you get all the values of the form field, and the background is easy to handle.
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Below, we only need a conversion class to fill the form value in HttpServletRequest into our custom SearchModel. Here we need a professional processing and conversion class: SearchFilterBuilder, first, convert the form value into a set of string types:
public static SearchFilterBuilder from(final HttpServletRequest request) { return new SearchFilterBuilder(request); } public List<String> buildToStrings() { return buildToStrings(true); } public List<String> buildToStrings(final boolean containsDataAuth) { List<String> searchFilterStrings = Lists.newArrayList(); Map<String, String[]> map = request.getParameterMap(); for (Map.Entry<String, String[]> 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; }
Based on the obtained condition set, we further parse the condition. Because the condition passed by the front-end View is string, a special regular expression class defasearchsearchfilterstringprocessor is used to parse the data.
public List<SearchFilter> build() { List<String> searchFilterStrings = buildToStrings(); List<SearchFilter> searchFilters = Lists.newArrayList(); searchFilters.addAll(searchFilterStrings.stream() .map(DefaultSearchFilterStringProcessor::from) .collect(Collectors.toList())); return searchFilters; }
Because the logic of string processing is not closely related to this article, no relevant code will be posted here. It is also possible to use the dumbest human flesh to parse strings without regular expressions, as mentioned above, after obtaining the Request object, you can perform subsequent operations.
3: If the condition in the form field is converted into a condition that can be recognized by the database component?
Tk. both mybatis and the official mybatis-spring components support dynamic conditions, because I use tk. mybatis, so some classes are tk. mybatis and tk are further encapsulated, so the principle is basically the same. As mentioned earlier, operating mybtis is a bit like operating native SQL. It feels like a process of SQL spelling, here, we can spell this dynamic condition as well. In simple cases, we only need a static conversion method. If we look at it further, we can find a way to achieve automatic identification and conversion, which is capable of research. The conversion is nothing more than the following:
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;
After solving the above problems, we can directly write the background Code as follows:
Controller:
@RequestMapping(value = "/getAllByPage") @ResponseBody public PageInfo<BcStore> getAllByPage(final SearchModel s1) { this.convertSearchModel(s1); return storeService.select(s1); }
Service: With the example object, paging information, and sorting fields, the following is the basic function of tk. mybatis.
@Override public final PageInfo<T> select(final SearchModel searchModel) { Example example = ExampleBuilder.forClass(genericType).fromSearchFilters(searchModel.getSearchFilters()).build(); return select(example, searchModel.getPageNum(), searchModel.getPageSize(), searchModel.getOrderBy()); }
Based on the preceding content, for query conditions, we can specify any conditions contained in the syntax query statement in the View. The controller and service in the background remain unchanged, it is enough to cope with common query on the management interface.