spring-boot学习:十四、spring-boot统一分页处理

分页处理在项目开发中是不可避免的步骤,本文主要是介绍基于Mybatis的分页。

一、推荐使用开源的pagehelper,简单好用,兼容性也强。

  1. 引入pagehelper

     com.github.pagehelper
     pagehelper-spring-boot-starter
     1.2.13
 
  1. 使用pagehelper
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();
        });
    }
}
  1. 测试
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;
	}
}

你可能感兴趣的:(spring-boot)