mybatis - pagehelper

kevincheung 2020-02-15

在开发过程中, 在获取列表的时候, 很多时候, 并不是一把拉出来展示, 更多的时候, 是以分页列表展示. 这时候, 就需要集成一个分页插件了: pagehelper

<dependency>
     <groupId>org.mybatis.spring.boot</groupId>
     <artifactId>mybatis-spring-boot-starter</artifactId>
     <version>1.3.2</version>
</dependency>

application.yml配置:

pagehelper:
    helperDialect: mysql
    #分页合理化, 针对不合理的分页自动处理
    resonable: true

加入一个 UserService:

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public PageInfo<User> getPageList(){
        PageHelper.startPage(1, 10);
        List<User> list = userMapper.getList();
        System.out.println(JSON.toJSONString(list));
        PageInfo<User> pageList = new PageInfo<>(list);
        return pageList;
    }
}

UserMapper.java 中加入一个方法:

List<User> getList();

UserMapper.xml 加入一个配置

<select id="getList" resultType="com.study.demo.mybatis.vo.User">
        select *  from user
    </select>

从例子上看, getList 并不是一个分页方法. 那么他又是如何分页呢? getList 即可以分页, 又可以不分页. 看起来神奇, 其实道理也很简单. 

假如在 getList() 方法中, 定义一个分页变量 doPage = false, 那么默认情况下, 他就是不分页的. 然后通过调用方法, 将 doPage 改成 true, 那么就在 sql 后面加上 " limit n, m " 语句, 完成分页.

事实上, pagehelper 确实是这么干的, 用的也是这套原理. 

1. PageHelperAutoConfiguration

@Configuration
@ConditionalOnBean({SqlSessionFactory.class})
@EnableConfigurationProperties({PageHelperProperties.class})
@AutoConfigureAfter({MybatisAutoConfiguration.class})
public class PageHelperAutoConfiguration {
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;
    @Autowired
    private PageHelperProperties properties;

    public PageHelperAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(
        prefix = "pagehelper"
    )
    public Properties pageHelperProperties() {
        return new Properties();
    }

    @PostConstruct
    public void addPageInterceptor() {
        PageInterceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        properties.putAll(this.pageHelperProperties());
        properties.putAll(this.properties.getProperties());
        interceptor.setProperties(properties);
        Iterator var3 = this.sqlSessionFactoryList.iterator();

        while(var3.hasNext()) {
            SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var3.next();
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
        }

    }
}

这里主要就是为 SqlSessionFactory 加入组件:  PageInterceptor

2. PageHelper.startPage(1, 10)

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
protected static boolean DEFAULT_COUNT = true;    

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

这里主要看 setLocalPage(page) 方法, 看看干了啥:

protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

LOCAL_PAGE.set(page) 就是存储了一个线程变量, 后面还可以通过 LOCAL_PAGE.get() 方法拿出这个变量. 

3. PageInterceptor

public Object intercept(Invocation invocation) throws Throwable {
        Object var16;
        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, 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);
            }

            var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if (this.dialect != null) {
                this.dialect.afterAll();
            }

        }

        return var16;
    }

3.1 this.dialect.skip()

public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        if (ms.getId().endsWith("_COUNT")) {
            throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");
        } else {
            Page page = this.pageParams.getPage(parameterObject, rowBounds);
            if (page == null) {
                return true;
            } else {
                if (StringUtil.isEmpty(page.getCountColumn())) {
                    page.setCountColumn(this.pageParams.getCountColumn());
                }

                this.autoDialect.initDelegateDialect(ms);
                return false;
            }
        }
    }

这里主要看一下 pageParams.getPage() 方法:

public Page getPage(Object parameterObject, RowBounds rowBounds) {
        Page page = PageHelper.getLocalPage();
        if (page == null) {
            if (rowBounds != RowBounds.DEFAULT) {
                if (this.offsetAsPageNum) {
                    page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), this.rowBoundsWithCount);
                } else {
                    page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, this.rowBoundsWithCount);
                    page.setReasonable(false);
                }

                if (rowBounds instanceof PageRowBounds) {
                    PageRowBounds pageRowBounds = (PageRowBounds)rowBounds;
                    page.setCount(pageRowBounds.getCount() == null || pageRowBounds.getCount());
                }
            } else if (parameterObject instanceof IPage || this.supportMethodsArguments) {
                try {
                    page = PageObjectUtil.getPageFromObject(parameterObject, false);
                } catch (Exception var5) {
                    return null;
                }
            }

            if (page == null) {
                return null;
            }

            PageHelper.setLocalPage(page);
        }

        if (page.getReasonable() == null) {
            page.setReasonable(this.reasonable);
        }

        if (page.getPageSizeZero() == null) {
            page.setPageSizeZero(this.pageSizeZero);
        }

        return page;
    }

这里的 PageHelper.getLocalPage() 执行的就是:  (Page)LOCAL_PAGE.get()

去线程中拿取存储的变量,

1. 如果拿到了, 则表示这个方法要分页, 去执行 ExecutorUtil.pageQuery() 方法

2. 如果拿不到, 则表示不用分页, 去执行 executor.query() 方法

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

getPageSql()最终会调用 MySqlDialect.java 中的 getPageSql() 方法:

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

3.3 afterAll()

在finally中, 执行了一个 Pagehelper.afterAll() 方法:

public void afterAll() {
        AbstractHelperDialect delegate = this.autoDialect.getDelegate();
        if (delegate != null) {
            delegate.afterAll();
            this.autoDialect.clearDelegate();
        }

        clearPage();
    }

看一下 clearPage() 方法:

public static void clearPage() {
        LOCAL_PAGE.remove();
    }

这里将 线程中存储的 page 删除掉了. 

分页的生命周期到这里就差不多结束了, 后面执行别的sql方法的时候, 就不会受到影响了.

相关推荐