列表页的动态条件搜索

我是如何做列表页的,我提到了列表页的动态条件搜索,主要的目的就是在View中能够动态的指定条件,而后端的数据查询逻辑尽量不变。之前在搞.net的时候,我们可以借助强大的ExpressionTree来解决,之前有一篇是微软的EntityFramework表达式转换:Linq to Entity经验:表达式转换,是将一种表达式转换成数据库组件能够识别的表达式,只不过那篇没有涉及到View中的条件而已。页面动态查询的最简单的方法就是解析View中特定的值来得到后台组件能够识别的查询逻辑。
 
  我们期待View中能够这样指定条件:

<input type="text" name="WHERE.storeName.LIKE" class="form-control" style="width: 180px;" " />

它的意思是查询字段storeName,操作符是like,看起来并不难,但要解决这么几个问题:

  1. 参数收集问题,表单域的值以什么样的方式提交到后台?
  2. 后台接收参数类型是什么?
  3. 如何将表单域中的条件转换成数据库组件能够识别的条件?

我们选择的数据库组件是mybatis+mysql。个人感觉mybatis在处理动态查询时比JPA在前期(技术学习前期,即水平还不太够的时候)要简单些,也可能是我对JPA的认识还不够,总感觉mybatis这种拼SQL的方式比较熟悉一些,也比较容易控制。当然它们的定位本身就不同,这里不多讨论。基于mybatis我们采用了tk.mybatis这个开源的组件,它的功能非常丰富,分页,通用mapper,代码生成等大部分功能都已经包含,大家有兴趣可以去搜索。

注:下面的功能是我的同事完成,这里我做为学习的过程来分享下,可能也有理解不到位的地方,纯属个人学习理解。其中有部分功能未展示出来(比如权限过滤,and or这些分组查询的支持等),只包含最基本的,每个项目的需求不同以及团队环境不同可以会有多种实现方式,选择大家都能接受的就可以了。

我们再分别看下上面的三个问题怎么解决:

  1. 参数收集问题,表单域的值以什么样的方式提交到后台方法?

一般做页面查询时请求数据就两种方式,get或者post。get一般是在采用了ajax这类技术,post就复杂一些,分为两种:一种也是采用ajax提交到后台,一种是表单的提交。这里呢,由于我们采用了angularjs,所以很显然只能采用ajax提交,如果查询条件多,可采用ajax的post。由于上面贴的代码片段显示条件的name是动态的,所以我们不可能定义一个具体的后台的业务Model对前台的条件,比如有name,email,phone等等,所以我们采用将表单域整个序列化后的结果传递到后台。

                 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:参数类型是什么?
     如果是表单提交方式,我们可以采用HttpServletRequest这个对象来接收所有表单域的值,但上一步我们采用的提交方式并非表单自身的提交,而是ajax的提交,ajax的请求,是不识别HttpServletRequest这个参数类型的,为此我们需要定义一个自定义的公共的对象来接收我们动态View中指定的条件,这里就有个我们的SearchModel,它包含如下内容:

  • 分页信息,当前页,页数据大小
  • 搜索条件信息集合List<SearchFilter>,一堆我们自己定义的条件,主要包含字段名称,操作符以及值,这里是本文的重点。
  • 转换为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;

上面搜索条件的信息,我们需要从View中获取,这里应用HandlerMethodArgumentResolver来解决,它只有两个方法:

  • 判断是否是支持的参数类型
boolean supportsParameter(MethodParameter parameter);

这个方法实现比较简单,只需要判断下当前的参数类型是否是指定的类型即可:

@Override
    public boolean supportsParameter(MethodParameter parameter) {
         Class<?> parameterType = parameter.getParameterType();
         return SearchModel.class.isAssignableFrom(parameterType);
    }
  • 解析数据的详细过程
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

这个方法是核心,之前有提到过,因为我们是ajax提交,在后台的controller方法中不能包含HttpServletRequest request 这个参数,但后台要想取表单域的值从哪取呢?其实还是从这个参数中取,只不过我们需要换一种方法,可以从上面接口的的webRequest对象中获取,有了这个对象也就意味着你得到了表单域的所有值了,后台的事情就好办了。

HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

下面只需要一个转换类将HttpServletRequest中的表单值填充到我们自定义的SearchModel中就可以了,这里需要一个专业处理转换有类SearchFilterBuilder,首先将表单值转换成一个集合,字符串类型的:

    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;
    }

基于上面得到的条件集合进一步解析条件,由于我们前端View传递的条件是字符串的,所以这里应用了一个专门的正则表达式的类DefaultSearchFilterStringProcessor去解析数据

   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;

    }

由于字符串处理的逻辑与本文关联并不大,这里就不贴相关代码了,不会正则的用最笨的人肉解析字符串也是可以的,如前面说的都拿到Request对象了后面都好操作。

3:如果将表单域中的条件转换成数据库组件能够识别的条件?
     tk.mybatis或者是官方的mybatis-spring组件都支持动态条件,由于我这采用的是tk.mybatis,所以某些类都是tk.mybatis的,tk是进一步的封装,所以原理大体是相同的,之前有提到过操作mybtis有点像操作原生SQL,感觉就是一种拼SQL的过程,这里我们拼这个动态条件也是类似,简单的话只需要一个静态转换方法就可以了,如果再深入一点可以想办法做成自动识别并转换,有能力的可研究。无非就是如下的转换:

             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 PageInfo<BcStore> getAllByPage(final SearchModel s1) {
        this.convertSearchModel(s1);

        return storeService.select(s1);
    }

service:有了example对象,分页信息,排序字段,后面的就是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());
    }

基于上面的内容,针对查询条件,我们可以在View中任意指定查语语句所包含的条件,后台的controller以及service基本保持不变,应付普通的管理界面查询足够了。

时间: 01-17

列表页的动态条件搜索的相关文章

Atitit.列表页and查询条件的最佳实践(1)------设定搜索条件and提交查询and返回json数据

Atitit.列表页and查询条件的最佳实践(1)------设置查询条件and提交查询and返回json数据 1. 1.?配置条件字段@Conditional 1 1 2. 2.?配置条件字段显示类型为[email protected](displayType?=?displayType.rang,?rangStart?=?rang.start,?rangEnd?=?rang.end,op=op.range) 1 3. #----show  condition  page  list 1 4.

django-xadmin列表页filter关联对象搜索问题

环境:xadmin-for-python3 python3.5.2 django1.9.12 问题描述:Product ProductSku两个实体,ProductSku FK外键关联Product ,Product 列表页的filter不支持productsku__sku_code的搜索,主要页面加载时报Product has no field named 'sku_code' 解决办法: xadmin\util.py文件中def get_model_from_relation(field):

列表页url参数格式分析【求指教】

运营对列表页url制定静态化模式,与区区观点相悖.遂请大家指教点解. 动态参数包含6个,分别是: 1认证(有机),2品类(水果),3地区(丰台),4状态(众筹中),5排序(评分),6分页 使用状态非常灵活,每个参数可能单独出现,也可能每n(1-6)个任意组合出现 一:传统方式 cert=1&tagid=2&county=1&status=1&order=star&p=1 当只需要其中部分参数的时候可以 方案1:cert=1&county=1 只取需要部分 方

dedecms讲解-arc.listview.class.php分析,列表页展示

./plus/list.php - 动态展示栏目列表页(也可能是频道封面) arc.listview.class.php 是dedecms的列表页的相关处理类 __construct()           // 初始化一些字段,变量CountRecord()           // 统计列表记录,总条目数,每页条目数,并对列表模板进行解析MakeHtml()              // 创建列表页HTML,主要是后台批量生成Display()               // 解析并展示

PHP.26-TP框架商城应用实例-后台2-商品列表页-搜索、翻页、排序

商品列表页 1.翻页 控制器GoodsController.class.php添加方法lst(),显示列表页 在商品模型GoodsModel.class.php类中添加search方法 /** *实现翻页.搜索.排序 * */ public function search($perPage = 5) //$perPage控制显示条数 { /***********翻页***********/ //取出总的记录数 $count = $this->count(); //生成翻页类的对象 $pageOb

kingadmin后台(2)、对象列表页功能开发

目录 页面展示 对象列表 过滤功能 搜索功能 action功能 排序 页面展示 对象列表 urls.py from django.conf.urls import url from king_admin import views urlpatterns = [ # model对象列表页 url(r'^(\w+)/(\w+)/$', views.model_obj_list, name='model_obj_list') ] views.py from django.shortcuts impor

仿百度壁纸client(五)——实现搜索动画GestureDetector手势识别,动态更新搜索keyword

仿百度壁纸client(五)--实现搜索动画GestureDetector手势识别,动态更新搜索关键字 百度壁纸系列 仿百度壁纸client(一)--主框架搭建,自己定义Tab + ViewPager + Fragment 仿百度壁纸client(二)--主页自己定义ViewPager广告定时轮播图 仿百度壁纸client(三)--首页单向.双向事件冲突处理,壁纸列表的实现 仿百度壁纸client(四)--自己定义上拉载入实现精选壁纸墙 仿百度壁纸client(五)--实现搜索动画Gesture

django-23.admin列表页优化和排序

前言 列表页优化和排序 ModelAdmin django的options.py里面 ModelAdmin类定义的参数可以设置admin后台列表页面,相关的参数如下 class ModelAdmin(BaseModelAdmin): """Encapsulate all admin options and functionality for a given model.""" list_display = ('__str__',) # 显示的字段

python测试开发django-23.admin列表页优化和排序

前言 列表页优化和排序 ModelAdmin django的options.py里面 ModelAdmin类定义的参数可以设置admin后台列表页面,相关的参数如下 class ModelAdmin(BaseModelAdmin): """Encapsulate all admin options and functionality for a given model.""" list_display = ('__str__',) # 显示的字段