分页处理在项目开发中是不可避免的步骤,本文主要是介绍基于Mybatis的分页。
一、推荐使用开源的pagehelper,简单好用,兼容性也强。
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.13
package com.kevin.fish.controller.demo;
import com.github.pagehelper.PageHelper;
import com.kevin.core.result.ApiResult;
import com.kevin.fish.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author kevin
* @create 2020/8/7 14:42
*/
@ApiResult
@RestController
public class TestController {
@Autowired
private UserService userService;
@GetMapping(value="/test",produces= MediaType.APPLICATION_JSON_VALUE)
public Object test(Integer pageNum, Integer pageSize){
return PageHelper.startPage(pageNum,pageSize).doSelectPage(()->{
userService.list();
});
}
}
http://localhost:8081/test?pageNum=1&pageSize=3
返回结果:
{"code":200,"text":null,"data":[{"id":"10","name":"Kevin"},{"id":"11","name":"Kevin"},{"id":"12","name":"css"}],"pageLimit":{"pageNum":1,"pageSize":3,"total":27}}
二、基于实现Interceptor自定义分页
Mybatis提供了一个很重要的接口类Interceptor,可以通过实现这个拦截器来完成自定义操作;
当然自己实现有点复杂,需要考虑性能,而且兼容性低,没有必要重复造轮子,了解大致的步骤就行。
以下仅提供实现分页的大致步骤:
@Component
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }) })
public class DialectInterceptor implements Interceptor {
protected Log logger = LogFactory.getLog(this.getClass());
private static Dialect dialect = null;
/** MappedStatement索引值 */
private static final int MAPPED_STATEMENT_INDEX = 0;
/** 参数索引值 */
private static final int PARAMETER_INDEX = 1;
/** RowBounds 索引值 */
private static final int ROWBOUNDS_INDEX = 2;
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前数据库方言
getDialect();
Object[] invocationArgs = invocation.getArgs();
MappedStatement ms = (MappedStatement) invocationArgs[MAPPED_STATEMENT_INDEX];
// 存储过程不分页
if (ms.getStatementType() != StatementType.CALLABLE) {
// 取出threadLocal中的分页参数
PageLimit pl = PageLimitHolderFilter.getContext();
// 原始SQL
String sql = ms.getBoundSql(invocationArgs[PARAMETER_INDEX]).getSql().trim();
// 分页处理
if (pl != null) {
logger.debug("开始分页操作进行SQL:" + sql);
// 重写count的sql
rewriteCount(sql, ms, invocationArgs);
// 执行count
Integer totalCount = ((List) invocation.proceed()).get(0);
// 设置分页的总页数
pl.setTotalCount(totalCount);
// 重写分页的sql
rewriteLimit(sql, ms, invocationArgs);
// 分页完成
pl.setLimited(true);
}
}
return invocation.proceed();
}
/**
* 使用方言重写count的sql
*
* @param sql
* @return
*/
private void rewriteCount(String sql, MappedStatement ms, Object[] args) {
BoundSql boundSql = ms.getBoundSql(args[PARAMETER_INDEX]);
args[ROWBOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
args[MAPPED_STATEMENT_INDEX] = copyFromMappedStatement(ms,
new BoundSqlSqlSource(copyFromBoundSql(ms, boundSql, dialect.getCountString(sql))), true);
}
/**
* 重写分页SQL
*
* @param sql
* @return
*/
private void rewriteLimit(String sql, MappedStatement ms, Object[] args) {
PageLimit pl = PageLimitHolderFilter.getContext();
int offset = pl.getStartRowNo() - 1;
int limit = pl.getPageLength();
BoundSql boundSql = ms.getBoundSql(args[PARAMETER_INDEX]);
if (dialect.supportsLimitOffset()) {
sql = dialect.getLimitString(sql, offset, limit);
offset = RowBounds.NO_ROW_OFFSET;
} else {
sql = dialect.getLimitString(sql, 0, limit);
}
// 本来的分页偏移设置失效(因为已经用原生SQL分页)
args[ROWBOUNDS_INDEX] = new RowBounds(offset, RowBounds.NO_ROW_LIMIT);
args[MAPPED_STATEMENT_INDEX] = copyFromMappedStatement(ms, new BoundSqlSqlSource(copyFromBoundSql(ms, boundSql, sql)), false);
// 分页后设置已经分过页
pl.setLimited(true);
}
private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
for (ParameterMapping mapping : boundSql.getParameterMappings()) {
String prop = mapping.getProperty();
if (boundSql.hasAdditionalParameter(prop)) {
newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
}
}
return newBoundSql;
}
private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource, boolean countMappedStatement) {
Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
// 如果是算count的话,返回结果为Integer型
if (countMappedStatement) {
List tmpList = new ArrayList();
tmpList.add(new ResultMap.Builder(ms.getConfiguration(), ms.getId() + "-count", Integer.class, new ArrayList())
.build());
builder.resultMaps(tmpList);
} else {
builder.resultMaps(ms.getResultMaps());
}
builder.resultSetType(ms.getResultSetType());
// setStatementCache()
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
public static class BoundSqlSqlSource implements SqlSource {
/** BoundSql对象 */
private BoundSql boundSql;
/**
* 构造方法
*
* @param boundSqlParam
* BoundSql对象
*/
public BoundSqlSqlSource(BoundSql boundSqlParam) {
this.boundSql = boundSqlParam;
}
/**
* getter for boundSql
*
* @param parameterObject
* Object
* @return BoundSql对象
*/
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
/**
* 得到数据库方言
*
* @return
*/
public static Dialect getDialect() {
if (dialect == null) {
try {
String database = PropertyUtils.getProperty("database");
if ("mysql".equals(database)) {
dialect = MysqlDialect.class.newInstance();
}
} catch (Exception e) {
throw new RuntimeException("分页类创建出错:" + dialect, e);
}
}
return dialect;
}
}