jeesite分页的使用

可行的分页姿势

jeesite的分页,限定了查询方法的参数只能有一个,多个参数就会失效。这个参数可以是以下三种类型:jeesite的Page、Page的子类、包含Page属性的类。
下面是几个试验:

//dao层的定义
/**
   * 参数:Page类型
   * 结果:分页成功
   */
 List<String> test(Page<String> page);
  /**
   * 参数:Page的子类
   * 结果:分页成功
   */
  List<String> test(ExtendPageEntity page);
  /**
   * 参数:包含Page属性的类
   * 结果:分页成功
   */
  List<String> test(WithPageEntity page);
  /**
   * 参数:完全跟Page没关系的类+Page类
   * 结果:分页失败
   */
  List<String> test(Page page,NoPageEntity entity);

实验用到的相关类型

public class ExtendPageEntity extends Page {
}
@Data
public class WithPageEntity {
    private Page page;
}
public class NoPageEntity {
}

只能存在一个参数,就很不习惯,jeesite的实体类都默认继承了个BaseEntity,里面都有Page属性,这就很容易造成我不想分页的时候,自动分页了,事实上也有同事踩过坑,所以下面先看下为什么只能有一个参数,其次尝试改造,让其可以向MybatiePlue的分页一样可以传多个参数,把Page独立出来。

源码探索

在mybatis-config.xml里面,我们可以看到分页插件的配置,定位到拦截的类。

    <plugins>
        <plugin interceptor="com.jeesite.common.persistence.interceptor.PaginationInterceptor"/>
    </plugins>
/**
 * 数据库分页插件,只拦截查询语句.
 *
 * @author poplar.yfyang / thinkgem
 * @version 2013-8-28
 */
@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class PaginationInterceptor extends BaseInterceptor {

    private static final long serialVersionUID = 1L;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

//        //拦截需要分页的SQL
        if (mappedStatement.getId().matches(_SQL_PATTERN)) {
//        if (StringUtils.indexOfIgnoreCase(mappedStatement.getId(), _SQL_PATTERN) != -1) {
        Object parameter = invocation.getArgs()[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        Object parameterObject = boundSql.getParameterObject();

        //获取分页参数对象
        Page<Object> page = null;
        if (parameterObject != null) {
            page = convertParameter(parameterObject, page);
        }

        //如果设置了分页对象,则进行分页
        if (page != null && page.getPageSize() != -1) {

            if (StringUtils.isBlank(boundSql.getSql())) {
                return null;
            }
            String originalSql = boundSql.getSql().trim();

            //得到总记录数
            page.setCount(SQLHelper.getCount(originalSql, null, mappedStatement, parameterObject, boundSql, log));

            //分页查询 本地化对象 修改数据库注意修改实现
            String pageSql = SQLHelper.generatePageSql(originalSql, page, DIALECT);
//                if (log.isDebugEnabled()) {
//                    log.debug("PAGE SQL:" + StringUtils.replace(pageSql, "\n", ""));
//                }
            invocation.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
            BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            //解决MyBatis 分页foreach 参数失效 start
            if (Reflections.getFieldValue(boundSql, "metaParameters") != null) {
                MetaObject mo = (MetaObject) Reflections.getFieldValue(boundSql, "metaParameters");
                Reflections.setFieldValue(newBoundSql, "metaParameters", mo);
            }
            //解决MyBatis 分页foreach 参数失效 end
            MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));

            invocation.getArgs()[0] = newMs;
        }
//        }
        return invocation.proceed();
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        super.initProperties(properties);
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms,
                                                    SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(),
                ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null) {
            for (String keyProperty : ms.getKeyProperties()) {
                builder.keyProperty(keyProperty);
            }
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.cache(ms.getCache());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    public static class BoundSqlSqlSource implements SqlSource {
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}

直接挑重点看,很明显一个给page赋值的地方:

page = convertParameter(parameterObject, page);
    /**
     * 对参数进行转换和检查
     * @param parameterObject 参数对象
     * @param page            分页对象
     * @return 分页对象
     * @throws NoSuchFieldException 无法找到参数
     */
protected static Page<Object> convertParameter(Object parameterObject, Page<Object> page) {
      try{
            if (parameterObject instanceof Page) {
                return (Page<Object>) parameterObject;
            } else {
                return (Page<Object>)Reflections.getFieldValue(parameterObject, PAGE);
            }
      }catch (Exception e) {
      return null;
    }
    }

convertParameter这个方法,首先是判断这个参数是否Page类型,是就直接返回自己就好了,否则进Reflections.getFieldValue()方法。

 /**
   * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
   */
  public static Object getFieldValue(final Object obj, final String fieldName) {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
      throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
    }

    Object result = null;
    try {
      result = field.get(obj);
    } catch (IllegalAccessException e) {
      logger.error("不可能抛出的异常{}", e.getMessage());
    }
    return result;
  }

很明显,里面就第一行里面的getAccessibleField方法是实际干活的。

/**
   * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
   * 
   * 如向上转型到Object仍无法找到, 返回null.
   */
  public static Field getAccessibleField(final Object obj, final String fieldName) {
    Validate.notNull(obj, "object can't be null");
    Validate.notBlank(fieldName, "fieldName can't be blank");
    for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
      try {
        Field field = superClass.getDeclaredField(fieldName);
        makeAccessible(field);
        return field;
      } catch (NoSuchFieldException e) {//NOSONAR
        // Field不在当前类定义,继续向上转型
        continue;// new add
      }
    }
    return null;
  }

很明显getAccessibleField这方法就是不断的尝试获取名为page的属性,没有就向上找父类的属性,直到object类结束。

从上面几个方法就可以看到,jeesite只会判断一个入参,然后判断这个参数是否Page类型或者是否包含Page属性,有就取里面的值加工sql。当有多个入参的时候,通过打断点debug发现,拦截器拿到的属性,就会转换成一个Map类型,虽然这个map里面的内容包含了page,但Map类型本身既不是Page的子类,也不包含Page属性,自然就拿不到page参数了。

改造让其允许多个入参

上面看完源码可以发现,问题在于jeesite的分页没有尝试去取map里面的内容,导致错过了page信息,那改造思路就很清晰了,判断入参类型是map,就遍历这个map,找出Page类型的参数,ok。
干活,改造convertParameter方法,其实就是加多一个if。

protected static Page<Object> convertParameter(Object parameterObject, Page<Object> page) {
      try{
            if (parameterObject instanceof Page) {
                return (Page<Object>) parameterObject;
            }
            if (parameterObject instanceof Map){
              Method method = parameterObject.getClass().getMethod("get",Object.class);
              return (Page<Object>) method.invoke(parameterObject, "page");
            }
            return (Page<Object>)Reflections.getFieldValue(parameterObject, PAGE);
      }catch (Exception e) {
        e.printStackTrace();
      return null;
    }
    }

结果发现分页还是失败,奇怪了,debug看下,发现map的key命名是0,1,param1,param2,就没有一个叫"page"的。那就便利这个map,每个成员值再判断下是否Page类型。

        if (parameterObject instanceof Map) {
          Page<Object> page1 = null;
          try {
            Map<String, Object> map = (Map<String, Object>) parameterObject;
            for (Map.Entry<String, Object> entry : map.entrySet()) {
              if (entry.getValue() instanceof Page) {
                page1 = (Page<Object>) entry.getValue();
                break;
              }
            }
          } catch (Exception e) {
          }
          return page1;
        }

结果很喜人,分页成功。
经过这番研究,其实还是看出了jeestie分页的一些问题,原版是根据命名取值,当入参对象里面的一个参数叫"page"时,就会取这个参数强转成Page类型,那如果我这个page不是Page类型呢,就出问题了吧。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值