PageHelperのページングの原則と大量のデータ量のクエリのパフォーマンスの問題に対する解決策

PageHelperページングの実装

PageHelper.startPage(1,3)

public static <E> Page<E> startPage(int pageNum, int pageSize) {
        return startPage(pageNum, pageSize, DEFAULT_COUNT);
    }
 
    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
        return startPage(pageNum, pageSize, count, (Boolean)null, (Boolean)null);
    }
 
    public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
        Page<E> page = startPage(pageNum, pageSize);
        page.setOrderBy(orderBy);
        return page;
    }
 
    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page<E> page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        Page<E> oldPage = getLocalPage();
        if(oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
 
        setLocalPage(page);
        return page;
    }

実際のselectステートメントの実行を開始します

public Page doSelectPage(ISelect select){ select.doSelect(); これを返します。}


MapperProxyクラスに入り、invokeメソッドを実行して、メソッド名とパラメーター値を取得します

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

次に、MapperMethodメソッドはexecuteステートメントを実行し、判断は追加、削除、変更、およびチェックです。複数の戻り値があると判断して、executeForManyメソッドを入力します

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

このメソッドは、SqlSessionTemplate、DefaultSqlSession、およびその他のクラスの呼び出しを開始して、Mapper.xmlファイルのSQLステートメントを取得します。

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

PageHelperの実際の実装の入力を開始すると、プラグインは動的プロキシのInvocationHandlerを実装することで関連情報を取得します

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
 
 
    try {
 
 
 
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
 
 
 
      if (methods != null && methods.contains(method)) {
 
 
 
        return interceptor.intercept(new Invocation(target, method, args));
 
 
 
      }
 
 
 
      return method.invoke(target, args);
 
 
 
    } catch (Exception e) {
 
 
 
      throw ExceptionUtil.unwrapThrowable(e);
 
 
 
    }
 
 
 
  }

PageInterceptorは、MybatisのInterceptorインターフェイスを実装してインターセプトします

public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement)args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds)args[2];
            ResultHandler resultHandler = (ResultHandler)args[3];
            Executor executor = (Executor)invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            if(args.length == 4) {
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                cacheKey = (CacheKey)args[4];
                boundSql = (BoundSql)args[5];
            }
 
            this.checkDialectExists();
            List resultList;
            if(!this.dialect.skip(ms, parameter, rowBounds)) {
                if(this.dialect.beforeCount(ms, parameter, rowBounds)) {
                    Long count = this.count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    if(!this.dialect.afterCount(count.longValue(), parameter, rowBounds)) {
                        Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
                        return var12;
                    }
                }
 
                resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
 
            Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
            return var16;
        } finally {
            this.dialect.afterAll();
        }
    }

ExecutorUtil抽象クラスのpageQueryメソッドに移動します

public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {
        if(!dialect.beforePage(ms, parameter, rowBounds)) {
            return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
        } else {
            parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
            String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
            BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
            Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
            Iterator var12 = additionalParameters.keySet().iterator();
 
            while(var12.hasNext()) {
                String key = (String)var12.next();
                pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
            }
 
            return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
        }
    }

抽象クラスAbstractHelperDialectのgetPageSqlで対応するPageオブジェクトを取得します

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
        String sql = boundSql.getSql();
        Page page = this.getLocalPage();
        String orderBy = page.getOrderBy();
        if(StringUtil.isNotEmpty(orderBy)) {
            pageKey.update(orderBy);
            sql = OrderByParser.converToOrderBySql(sql, orderBy);
        }
 
        return page.isOrderByOnly()?sql:this.getPageSql(sql, page, pageKey);
    }

MySqlDialectクラスのgetPageSqlメソッドを入力してSQLをカプセル化し、ページオブジェクト情報に従って制限を増やします。これは、ページ化された情報が組み立てられる方法です

public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        sqlBuilder.append(sql);
        if(page.getStartRow() == 0) {
            sqlBuilder.append(" LIMIT ? ");
        } else {
            sqlBuilder.append(" LIMIT ?, ? ");
        }
 
        return sqlBuilder.toString();
    }

最終的にアセンブルされたSQLをDefaultSqlSessionに返して、クエリを実行し、

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }

PageHelperのページング機能は、SQLのスプライシングを制限することで実現されます。

ここで問題が発生
します。SQLステートメントを分析する場合、データ量が少ない場合やページ数が比較的多い場合、limitのクエリ効率は比較的高くなります。(テスト用の数百万のデータを含む単一のテーブル)

select * from user where age = 10 limit1,10;結果は0.43秒を示します

where条件の後に設定された結果が大きく、ページ数が1桁に達すると、SQL全体のクエリ効率は非常に低くなります(where条件にインデックスが追加されている場合でも)。

select * from user where age = 10 limit100000,10;結果は4.73sを示しています

それで、解決策は何ですか?

ページングスキーム
元のページングSQL


select * from table name limit155555,201
最適化されたSQLステートメント

select *
FROM table name WHERE'id' in(select id from table name LIMIT 155555,20)

最初に開くPageHelper.startPage(pageParam.getPageNum()、pageParam.getPageSize()); 155555,20は必然的にSQLの後ろに追加され、最適化されたSQLの制限はサブクエリステートメントです。前の練習に

SELECT*FROMテーブル名WHERE'id'in(テーブル名からidを選択)LIMIT 155555,20

問題解決

PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize());
        //在这里算出select id from 表名 LIMIT 0,30
        List<Integer> pageProcessLogList = processLogMapper.getCount(processLog);

        if (pageProcessLogList.size() == 0) {
            processLog.setIds(null);
        } else {
            processLog.setIds(pageProcessLogList);
        }
        
        //这里我们不用processlists查询的数量,而是用pageProcessLogList 
        PageInfo<Integer> countInfo = new PageInfo<>(pageProcessLogList);
        List<ProcessLog> processlists =processLogMapper.pageProcessLogList(processLog);
        PageInfo<ProcessLog> infolist = new PageInfo<>(processlists);
        //countinfo含有的信息是pagehepler封装过的,我们换个对象
        BeanUtils.copyProperties(countInfo,infolist);
        
        infolist.setList(processlists);
        return infolist;

マッパーファイル

Mapper

``java
<select id="getCount" resultType="java.lang.Integer" parameterType="com.tvunetworks.useractivity.model.ProcessLog">
        select id from
        <choose>
            <when test="dateSuffix.length()==6">
                t_process_email_log${dateSuffix}
            </when>
            <otherwise>
                t_process_log${dateSuffix}
            </otherwise>
        </choose>
        <where>
            <if test="serverName != null and serverName != ''">
                `server_name` = #{serverName}
            </if>
            <if test="method != null and  method != ''">
                and `method` = #{method}
            </if>
            <if test="requestUri != null and requestUri != ''">
                and `request_uri` like CONCAT('%',#{requestUri},'%')
            </if>
            <if test="params != null and params != ''">
                and `params` like CONCAT('%',#{params},'%')
            </if>
            <if test="user != null and user != ''">
                and `user` like CONCAT('%',#{user},'%')
            </if>
            <if test="status != null and status != ''">
                and `status` = #{status}
            </if>
            <if test="result != null and result != ''">
                and `result` like CONCAT('%',#{result},'%')
            </if>
            <if test="requestTime != null and requestTime != ''">
                and `request_time` >= #{requestTime}
            </if>
            <if test="responseTime != null and responseTime != ''">
                and `response_time` = #{responseTime}
            </if>
            <if test="email != null and email != ''">
                and `email` like CONCAT('%',#{email},'%')
            </if>
            <if test="peerId != null and peerId != ''">
                and `peer_id` like CONCAT('%',#{peerId},'%')
            </if>
            <if test="ip != null and ip != ''">
                and `ip` like CONCAT('%',#{ip},'%')
            </if>
            <if test="userAgent != null and userAgent != ''">
                and `user_agent` like CONCAT('%',#{userAgent},'%')
            </if>
            <if test="dpi != null and dpi != ''">
                and `dpi` like CONCAT('%',#{dpi},'%')
 
```sql
<select id="pageProcessLogList" resultType="com.tvunetworks.useractivity.model.ProcessLog">
        select * from
        <choose>
            <when test="dateSuffix.length()==6">
                表名${dateSuffix} a
            </when>
            <otherwise>
               表名${dateSuffix} a
            </otherwise>
        </choose>
        <where>
            <if test="ids!=null">
                id in
                <foreach collection="ids" item="s" open=" (" separator="," close=")">
                    #{s}
                </foreach>
            </if>

            <if test="serverName != null and serverName != ''">
                and `server_name` = #{serverName}
            </if>
            <if test="method != null and  method != ''">
                and `method` = #{method}
            </if>
            <if test="requestUri != null and requestUri != ''">
                and `request_uri` like CONCAT('%',#{requestUri},'%')
            </if>
            <if test="params != null and params != ''">
                and `params` like CONCAT('%',#{params},'%')
            </if>
            <if test="user != null and user != ''">
                and `user` like CONCAT('%',#{user},'%')
            </if>
            <if test="status != null and status != ''">
                and `status` = #{status}
            </if>
            <if test="result != null and result != ''">
                and `result` like CONCAT('%',#{result},'%')
            </if>
            <if test="requestTime != null and requestTime != ''">
                and `request_time` >= #{requestTime}
            </if>
            <if test="responseTime != null and responseTime != ''">
                and `response_time` = #{responseTime}
            </if>
            <if test="email != null and email != ''">
                and `email` like CONCAT('%',#{email},'%')
            </if>
            <if test="peerId != null and peerId != ''">
                and `peer_id` like CONCAT('%',#{peerId},'%')
            </if>
            <if test="ip != null and ip != ''">
                and `ip` like CONCAT('%',#{ip},'%')
            </if>
            <if test="userAgent != null and userAgent != ''">
                and `user_agent` like CONCAT('%',#{userAgent},'%')
            </if>
            <if test="dpi != null and dpi != ''">
                and `dpi` like CONCAT('%',#{dpi},'%')
            </if>
        </where>
        <if test="sortModel != null and sortModel != ''">
            order by request_time ${sortModel}
        </if>
    </select>
```</if>
        </where>
    </select>

おすすめ

転載: blog.csdn.net/liuerchong/article/details/123941167