在開發過程中, 在獲取列表的時候, 很多時候, 並不是一把拉出來展示, 更多的時候, 是以分頁列表展示. 這時候, 就需要集成一個分頁插件了: 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方法的時候, 就不會受到影響了.