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方法的时候, 就不会受到影响了.