20、PageHelper分页插件使用及原理

对着背影说爱祢 提交于 2020-10-18 03:21:52

1、逻辑分页与物理分页

逻辑分页: 也叫内存分页, 逻辑分页依赖的是程序员编写的代码. 数据库返回的不是分页结果, 而是全部数据,然后再由程序员通过
         代码获取分页数据, 取出全部数据先存于内存中, 再取出需要的数据常用的操作是一次性从数据库中查询出全部数据并存储
         到List集合中,因为List集合有序,再根据索引获取指定范围的数据.
		 
物理分页: 物理分页依赖的是某一物理实体, 这个物理实体就是数据库, 即在查询数据库时在库里执行分页,比如MySQL数据库提供
         了limit关键字,程序员只需要编写带有limit关键字的SQL语句,数据库返回的就是分页结果.
		 
 Mybatis的RowBounds属于内存分页, pageHelper属于物理分页

2、PageHelper使用

// 1. 引入依赖, 使用SpringBoot方式配置时, 有一个自动配置类PageHelperAutoConfiguration, 会自动配置一个Interceptor
<dependency>
       <groupId>com.github.pagehelper</groupId>
       <artifactId>pagehelper-spring-boot-starter</artifactId>
       <version>1.2.10</version>
</dependency>

// 2. 在代码中使用
PageHelper.startPage(pageNo, pageSize, orderString);
// 如果不使用SpringBoot方式, 也可以手动在mybatis-config.xml中配置一个PageInterceptor
<plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时, 会将RowBounds第一个参数offset当成pageNum页码使用 -->
            <!-- 和startPage中的pageNum效果一样-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时, 使用RowBounds分页会进行count查询 -->
            <property name="rowBoundsWithCount" value="false"/>
            <!-- 设置为true时, 如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
            <!-- (相当于没有执行分页查询, 但是返回结果仍然是Page类型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
            <!-- 启用合理化时, 如果pageNum<1会查询第一页, 如果pageNum>pages会查询最后一页 -->
            <!-- 禁用合理化时, 如果pageNum<1或pageNum>pages会返回空数据 -->
            <property name="reasonable" value="false"/>
            <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
            <!-- 增加了一个`params`参数来配置参数映射, 用于从Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
            <!-- 不理解该含义的前提下, 不要随便复制该配置 -->
            <property name="params" value="pageNum=start;pageSize=limit;"/>
        </plugin>
</plugins>

3、PageHelper的原理

@Configuration
@ConditionalOnBean(SqlSessionFactory.class)
@EnableConfigurationProperties(PageHelperProperties.class)
// 创建这个bean的前提是有MybatisAutoConfiguration
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class PageHelperAutoConfiguration {

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @Autowired
    private PageHelperProperties properties;

    /**
     * 接受分页插件额外的属性
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
    public Properties pageHelperProperties() {
        return new Properties();
    }

    @PostConstruct
    public void addPageInterceptor() {
        PageInterceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        //先把一般方式配置的属性放进去
        properties.putAll(pageHelperProperties());
        //在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
        properties.putAll(this.properties.getProperties());
        interceptor.setProperties(properties);
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
	    // 手动给Mybatis的插件链中加入一个PageInterceptor
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
        }
    }

}
// 1. 设置
PageHelper.startPage(pageNo, pageSize, orderString);

// 2. 查询SQL

// 3. 查询SQL最终会走到这里SqlSessionTemplate的invoke方法
private class SqlSessionInterceptor implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	// sqlSession创建完之后, 发现它的executor里面的interceptor就有一个PageInterceptor了, 所以下面看下它的创建过程
        SqlSession sqlSession = getSqlSession(
                                      SqlSessionTemplate.this.sqlSessionFactory,
                                      SqlSessionTemplate.this.executorType,
                                      SqlSessionTemplate.this.exceptionTranslator);
        try {
            Object result = method.invoke(sqlSession, args);
            return result;
        } 
    } 
}

// getSqlSession进去到这一步里面开始创建sqlSession
session = sessionFactory.openSession(executorType);

// 接着到这一步骤
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);

// configuration.newExecutor(tx, execType)到这一步骤
executor = (Executor) interceptorChain.pluginAll(executor);

// 走到这一步创建动态代理对象
public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        return Proxy.newProxyInstance(type.getClassLoader(),
                                      interfaces,
                                      new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

// 执行查询
Object result = method.invoke(sqlSession, args);

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)) {
	     // 这个interceptor是PageInterceptor, 走到这里面就去进行分页逻辑了. 会给你的SQL拼装上分页参数
             return interceptor.intercept(new Invocation(target, method, args));
         }
         return method.invoke(target, args);
     } 
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!