
?> 學習目的: 分頁的好處就是減少數據的處理量
mybatis框架分頁實現,常用得幾種方式,最簡單的就是
sql分頁,利用原生的sql關鍵字limit來實現;(不推薦)
還有一種就是利用interceptor來拼接sql,實現和limit一樣的功能;(不推薦)
再一個就是利用PageHelper來實現。這里講解這三種常見的實現方式 。
攔截器分頁。( 數據量大時,實現攔截器就很有必要了)
!> 注意:分頁的實現,是基於 SpringBoot整合MyBatis 之上。
1、pagehelper 分頁
添加相關依賴
首先,我們需要在 pom.xml 文件中添加分頁插件依賴包。
pom.xml
<!-- pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency>
添加相關配置
然后在 application-dev.yml 配置文件中添加分頁插件有關的配置。
application-dev.yml
# pagehelper
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
添加分頁配置
分頁查詢請求封裝類。
PageRequest.java
import lombok.Data; @Data public class PageRequest { /**當前頁碼*/ private int pageNum; /**每頁數量*/ private int pageSize; }
分頁查詢結果封裝類。
PageResult.java
import lombok.Data; import java.util.List; @Data public class PageResult { /** 當前頁碼 */ private int pageNum; /** 每頁數量 */ private int pageSize; /** 記錄總數 */ private long totalSize; /** 頁碼總數 */ private int totalPages; /** 數據模型*/ private List<?> content; }
分頁查詢相關工具類。
PageUtils.java
import com.github.pagehelper.PageInfo; public class PageUtils { /** * 將分頁信息封裝到統一的接口 * @param pageInfo * @return */ public static PageResult getPageResult(PageInfo<?> pageInfo) { PageResult pageResult = new PageResult(); pageResult.setPageNum(pageInfo.getPageNum()); pageResult.setPageSize(pageInfo.getPageSize()); pageResult.setTotalSize(pageInfo.getTotal()); pageResult.setTotalPages(pageInfo.getPages()); pageResult.setContent(pageInfo.getList()); return pageResult; } }
添加代碼
UserMapper.xml
<!-- 查詢分頁 --> <select id="getAllUserByPage" resultMap="BaseResultMap"> <include refid="Base_Column_List" /> from db_user </select>
dao(UserMapper.java)
/** 分頁 */ List<User> getAllUserByPage();
service(IUserService.java && UserServiceImpl.java)
// IUserService /** * 分頁查詢接口 * 這里統一封裝了分頁請求和結果,避免直接引入具體框架的分頁對象, 如MyBatis或JPA的分頁對象 * 從而避免因為替換ORM框架而導致服務層、控制層的分頁接口也需要變動的情況,替換ORM框架也不會 * 影響服務層以上的分頁接口,起到了解耦的作用 * @param pageRequest 自定義,統一分頁查詢請求 * @return PageResult 自定義,統一分頁查詢結果 */ PageResult getAllUserByPage(PageRequest pageRequest); // UserServiceImpl /** 分頁查詢 */ @Override public PageResult getAllUserByPage(PageRequest pageRequest) { return PageUtils.getPageResult(getPageInfo(pageRequest)); } /** * 調用分頁插件完成分頁 * @param pageRequest * @return */ private PageInfo<User> getPageInfo(PageRequest pageRequest) { int pageNum = pageRequest.getPageNum(); int pageSize = pageRequest.getPageSize(); PageHelper.startPage(pageNum, pageSize); List<User> sysMenus = userMapper.getAllUserByPage(); return new PageInfo<User>(sysMenus); }
controller(UserController.java)
// pagehelper 分頁 post 請求 @PostMapping("/findPage") public Result findPage(@RequestBody PageRequest pageQuery) { return Result.success(userService.getAllUserByPage(pageQuery)); }
測試
測試工具:postman(自己用自己喜歡的工具)
測試路徑:http://localhost:8081/findPage
結果圖:

2、攔截器分頁
實現原理主要是在數據庫執行session查詢的過程中,修改sql語句,先查詢記錄總條數,然后再分頁查詢數據記錄,再把數據整合成分頁數據形式就可以了。

添加相關配置
application-dev.yml
mybatis: mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: com.mmdz.entity # sql 打印 # configuration: # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl config-location: classpath:mybatis/mybatis-config.xml
注意:要注釋 configuration ,否則會報錯
IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together。// 配置重復導致沖突
Caused by: java.lang.IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together
at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.1.jar:5.3.1]
at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:488) ~[mybatis-spring-2.0.6.jar:2.0.6]
at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:633) ~[mybatis-spring-2.0.6.jar:2.0.6]
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="false" /><!-- 暫時禁用緩存 --> <setting name="logImpl" value="STDOUT_LOGGING"/><!-- 打印sql--> </settings> <plugins> <plugin interceptor="com.mmdz.common.interceptor.PaginationInterceptor"></plugin> </plugins> </configuration>
添加攔截器代碼和配置
PaginationInterceptor.java
注意:這個類攔截StatementHandler類的prepare方法,解析獲取請求方法參數。如果是單個參數,且參數類型為SimplePage,則采用分頁模式。或者有多個參數,並且其中一個參數被標注為page(即接口中@Param("page")),也啟用分頁模式。其中getCountSql(String sql)方法和getPageSql(String sql, SimplePage page)方法需要根據不同數據庫進行修改,我用Mysql。
import com.mmdz.common.interceptor.page.SimplePage; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ReflectorFactory; import org.apache.ibatis.reflection.factory.DefaultObjectFactory; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; /** * @Author: MMDZ * @Date: 2021/5/31 * @Desc: 分頁攔截器mybatis */ @Intercepts( { @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class , Integer.class}) }) public class PaginationInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class); private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory(); private static String dialect = "mysql"; @Override public Object intercept(Invocation invocation) throws Throwable { // 獲得攔截的對象 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); // 待執行的sql的包裝對象 BoundSql boundSql = statementHandler.getBoundSql(); // 判斷是否是查詢語句 if (isSelect(boundSql.getSql())) { // 獲得參數集合 Object params = boundSql.getParameterObject(); if (params instanceof Map) { // 請求為多個參數,參數采用Map封裝 return complexParamsHandler(invocation, boundSql, (Map<?, ?>) params); } else if (params instanceof SimplePage) { // 單個參數且為Page,則表示該操作需要進行分頁處理 return simpleParamHandler(invocation, boundSql, (SimplePage) params); } } return invocation.proceed(); } private Object complexParamsHandler(Invocation invocation, BoundSql boundSql, Map<?, ?> params) throws Throwable { //判斷參數中是否指定分頁 if (containsPage(params)) { return pageHandlerExecutor(invocation, boundSql, (SimplePage) params.get("page")); } else { return invocation.proceed(); } } private boolean containsPage(Map<?, ?> params) { if(params==null){ return false; }else if(!params.containsKey("page")){ return false; } Object page = params.get("page"); if(page==null){ return false; }else if(page instanceof SimplePage){ return true; } return false; } private boolean isSelect(String sql) { if (!StringUtils.isEmpty(sql) && sql.toUpperCase().trim().startsWith("SELECT")) { return true; } return false; } private Object simpleParamHandler(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable { return pageHandlerExecutor(invocation, boundSql, page); } private Object pageHandlerExecutor(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable { // 獲得數據庫連接 Connection connection = (Connection) invocation.getArgs()[0]; // 使用Mybatis提供的MetaObject,該對象主要用於獲取包裝對象的屬性值 MetaObject statementHandler = MetaObject.forObject(invocation.getTarget(), DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY); // 獲取該sql執行的結果集總數 int maxSize = getTotalSize(connection, (MappedStatement) statementHandler.getValue("delegate.mappedStatement"), boundSql); // 生成分頁sql page.setTotalRecord(maxSize); String wrapperSql = getPageSql(boundSql.getSql(), page); MetaObject boundSqlMeta = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY); // 修改boundSql的sql boundSqlMeta.setValue("sql", wrapperSql); return invocation.proceed(); } private int getTotalSize(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) { String countSql = getCountSql(boundSql.getSql()); PreparedStatement countStmt; ResultSet rs; List<AutoCloseable> closeableList = new ArrayList<AutoCloseable>(); try { countStmt = connection.prepareStatement(countSql); BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), boundSql.getParameterObject()); setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject()); rs = countStmt.executeQuery(); if (rs.next()) { return rs.getInt(1); } closeableList.add(countStmt); closeableList.add(rs); } catch (SQLException e) { logger.error("append an exception[{}] when execute sql[{}] with {}", e, countSql, boundSql.getParameterObject()); } finally { for (AutoCloseable closeable : closeableList) { try { if (closeable != null) closeable.close(); } catch (Exception e) { logger.error("append an exception[{}] when close resource[{}] ", e, closeable); } } } return 0; } private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException { ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler.setParameters(ps); } @Override public Object plugin(Object target) { // 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的次數 if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } } @Override public void setProperties(Properties properties) { } public String getCountSql(String sql) { if("mysql".equals(dialect)){ return "select count(0) from (" + sql + ") as total"; } return sql; } public String getPageSql(String sql, SimplePage page) { if(page.getPage()<=0){ page.setPage(1); } if(page.getRows()<=0){ page.setRows(20); } int startRow = (page.getPage()-1)*page.getRows(); if(startRow>=page.getTotalRecord()){ page.setPage(1); startRow=0; } if("mysql".equals(dialect)){ return sql+" limit "+startRow+", "+page.getRows(); } return sql; } }
SimplePage.java
注意:這個類是分頁對象所需的最簡單的類,只要包含頁碼、每頁條數、總條數和數據記錄,就可以推算出所有需要的分頁參數。所以用這個類來給攔截器做參數判斷,若需要更多的頁碼信息可以重寫一個分頁類繼承這個SimplePage即可。
import lombok.Getter; import lombok.Setter; import java.io.Serializable; import java.util.List; @Getter @Setter public class SimplePage implements Serializable { protected static final long serialVersionUID = 5136213157391895517L; protected int page = 1;// 頁碼,默認是第一頁 protected int rows = 10;// 每頁顯示的記錄數,默認是10 protected int totalRecord;// 總記錄數 protected List data;// 當前頁記錄 public SimplePage setData(List data) { this.data = data; return this; } }
Page.java
注意:這個Page類主要是豐富SimplePage類,最重要的就是 setData(List data)方法,由這個方法來豐富一些變量數據。
import lombok.Getter; import lombok.Setter; import java.util.List; /** * @Author: MMDZ * @Date: 2021/5/31 * @Desc: 這個Page類主要是豐富SimplePage類,最重要的就是 setData(List data)方法, * 由這個方法來豐富一些變量數據。 */ @Getter @Setter public class Page extends SimplePage { private static final long serialVersionUID = -6190845403265328029L; private boolean isFirstPage = true;//是否是第一頁 private boolean isLastPage = false;//是否是最后一頁 private int pageCount = 0;//當前頁總記錄數 private int totalPage = 0;//總頁數 private int prePage = 1;//上一頁頁碼 private int nextPage = 1;//下一頁頁碼 public Page() { super(); } public Page(int page, int rows) { super(); setPage(page); setRows(rows); } @Override public Page setData(List data){ super.setData(data); if(data!=null && data.size()>0){ pageCount = data.size(); if(this.page==1){ isFirstPage=true; }else{ isFirstPage=false; } //*** totalPage = (int)Math.ceil(totalRecord/(double)rows); //*** if(page==totalPage){ isLastPage = true; }else{ isLastPage = false; } //*** if(isFirstPage){ prePage = 1; }else{ prePage = page-1; } //*** if(isLastPage){ nextPage = 1; }else{ nextPage = page+1; } }else{ isLastPage = true; } return this; } }
添加代碼
UserMapper.xml
<select id="findPage" resultMap="BaseResultMap"> <include refid="Base_Column_List" /> from db_user </select>
UserMapper.java
注意:如果這個查詢方法只有分頁參數page,沒有別的參數,可以不寫@Param("page"),若有多個參數必須用@Param標明參數名,這是攔截器判斷分頁的依據。
/** @Param("page")是應分頁插件要求編寫的 */ List<User> findPage(@Param("page") Page page);
UserServiceImpl.java
/** * 攔截器分頁 * @param page * @return */ @Override public Page findPage(Page page) { List<User> list = userMapper.findPage(page); page.setData(list); return page; }
IUserService.java
/** * 攔截器分頁 * @param page * @return */ Page findPage(Page page);
UserController.java
// 攔截器 分頁 post 請求 @PostMapping("/findPage2") public Result findPage(@RequestBody Page page){ return Result.success(userService.findPage(page)); }
測試
測試工具:postman(自己用自己喜歡的工具)
測試路徑:http://localhost:8081/findPage2
結果圖:

