HengYk Blog

个人站

具有浪漫主义情调的理想主义务实青年


Mybatis分页原理

博文参考

MyBatis - 分页之四大方式(数组、SQL、拦截器,RowBounds)

MyBatis - 拦截器分页(原理机制 + 功能进阶)

数组分页

数据库查询并返回所有的数据,而我们需要的只是极少数符合要求的数据。

当数据量少时,还可以接受。

当数据库数据量过大时,每次查询对数据库和程序的性能都会产生极大的影响。

    @Override
    public List<Address> queryAddressByArray(int currPage, int pageSize) {

        List<Address> addressList = addressDao.queryAddressByArray();

        int firstIndex = (currPage - 1) * pageSize;
        int lastIndex = currPage * pageSize;

        return addressList.subList(firstIndex, lastIndex);
    }

5cd1003088aeb

SQL分页

实现了按需查找,每次检索得到的是指定的数据。

每次在分页的时候都需要去编写limit语句,很冗余。

不方便统一管理,维护性较差。

    <select id="queryAddressBySql" parameterType="map" resultType="Address">
        select * from j_position limit #{currIndex}, #{pageSize}
    </select>

5cd1005a7f555

拦截器分页

实现了按需查找,且适用于不同类型的数据库。

不再需要在每个语句中单独去配置分页相关的参数了,可以同时适用所有需要分页的场景。

    @Intercepts(@Signature(type = StatementHandler.class, method = "prepare",args = {Connection.class, Integer.class}))
public class MyPageInterceptor implements Interceptor {

    private int currPage;

    private int pageSize;

    private String dbType;

    /**
     * 拦截过程
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation)
throws Throwable {

        ...

        return invocation.proceed();
    }

    /**
     * 获取代理对象
     *
     * @param o
     * @return
     */
    @Override
    public Object plugin(Object o) {

        return Plugin.wrap(o, this);
    }

    /**
     * 设置代理对象的参数
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {

        String limitStr = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limitStr);
        this.dbType = properties.getProperty("dbType", "mysql");
    }
    <plugins>
        <plugin interceptor="cn.edu.xidian.ictt.springyk.interceptor.MyPageInterceptor">
            <property name="limit" value="10"/>
            <property name="dbType" value="mysql"/>
        </plugin>
    </plugins>

5cd1006edb5fd

RowBounds分页

和数组分页的原理类似,一次性将数据加载到内存。

    <select id="queryAddressByRowBounds" resultType="Address">
        select * from j_position
    </select>
    /**
     * RowBounds分页
     *
     * @return
     */
    List<Address> queryAddressByRowBounds(RowBounds rowBounds);
    /**
     *
     * @param currPage
     * @param pageSize
     * @return
     */
    List<Address> queryAddressByRowBounds(int currPage,
                                    int pageSize);
}
    @Override
    public List<Address> queryAddressByRowBounds(int currPage,
                                        int pageSize) {
        return addressDao.queryAddressByRowBounds(
                new RowBounds((currPage - 1) * pageSize, pageSize));
    }
    @ResponseBody
    @RequestMapping("/address/RowBounds/{currPage}/{pageSize}")
    public List<Address> getAddressByRowBounds(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
        return addressService.queryAddressByRowBounds(currPage, pageSize);
    }

5cd1007d79d34

Github完整代码

https://github.com/HengYk/springyk

源码分析

        RoutingStatementHandler是StatementHandler的默认实现类。    

        RoutingStatementHandler只是一个中间代理类,负责将StatementHandler导向三个不同的服务对象,这三个服务对象继承自BaseStatementHandler:

        SimpleStatementHandler是默认的简单执行器;

        PreparedStatementHandler是重用预处理语句的执行器;   

        CallableStatementHandler是重用语句和批量更新的处理器。

package org.apache.ibatis.executor.statement;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

/**
 * @author Clinton Begin
 */
public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }
    ...
}

        BaseStatementHandler属性分析:

        Configuration包含了Mybatis的所有配置信息;

        ResultSetHandler用于处理拦截的执行结果;

        ParameterHandler用于处理拦截的SQL参数;

        Executor是SQL的执行器;

        MappedStatement存储了Mybatis中一条SQL语句的所有配置信息;

        RowBounds可直接用于分页,封装了offset和limit;          

        BoundSql用于处理拦截的SQL语句。   

public abstract class BaseStatementHandler implements StatementHandler {

  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;

  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;

  protected BoundSql boundSql;
  }