mybatis - pagehelper
在开发过程中, 在获取列表的时候, 很多时候, 并不是一把拉出来展示, 更多的时候, 是以分页列表展示. 这时候, 就需要集成一个分页插件了: 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方法的时候, 就不会受到影响了.