先上一下最終的效果:
通過傳入頁數page 和 分頁大小size後 只需要傳遞特定的分頁對象(Pageable)給Mapper後 無需任何操作即可實現分頁
效果,類似於spring data jpa 以及 mybatis-plus的分頁功能。
一.檔案結構以及配置
build.gradle:
buildscript {ext {springBootVersion = '1.5.9.RELEASE'}repositories {mavenCentral()}dependencies {classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")}}apply plugin: 'java'apply plugin: 'idea'apply plugin: 'org.springframework.boot'group = 'org.zhu'version = '0.0.1-SNAPSHOT'sourceCompatibility = 1.8repositories {mavenCentral()}dependencies {compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')compile('org.springframework.boot:spring-boot-starter-web')runtime('mysql:mysql-connector-java')testCompile('org.springframework.boot:spring-boot-starter-test')}
檔案結構:
mybatis配置:
application.properties:
# ===============================# = DATA SOURCE# ===============================spring.datasource.url = jdbc:mysql://localhost:3306/test?createDatabaseIfNotExist=true&characterEncoding\=utf-8spring.datasource.userName = rootspring.datasource.password = spring.datasource.testWhileIdle = truespring.datasource.validationQuery = SELECT 1# ===============================# = mybatis# ===============================mybatis.mapper-locations = classpath*:mapper/*.xmlmybatis.check-config-location=truemybatis.config-location=classpath:mybatis/mybatis-config.xmlmybatis.type-aliases-package = org.zhu.mybatis_test.bean
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="mapUnderscoreToCamelCase" value="true" /> </settings> <!--設定攔截器--> <plugins> <plugin interceptor="org.zhu.mybatis_test.PageInterceptor"> <property name="dialect" value="mysql" /> </plugin> </plugins></configuration>
二.實現
1.總體思路
spring Data Jpa 的分頁很便捷
每次查詢的時候只需要傳遞Pageable對象,即可返回分頁的資訊: "content":{}, "last": false, "totalPages": 2, "totalElements": 21, "size": 20, "number": 0, "sort": null, "first": true, "numberOfElements": 20
本外掛程式的分頁 如下所示:
通過傳入自訂的Pageable對象,動態為對應的sql語句加上Limit xx,xx 來實現物理分頁,並且自動查詢分頁總數,動態加入
到自訂的Pageable對象中,然後返回查詢的List列表,手動的把返回內容加入到自訂的Pageable對象中返回。
2.實現mybatis外掛程式攔截器
如果要實現如上的某些行為,讓分頁全程自動化運行,那麼就需要在mapper對應的方法執行過程中,攔截並且修改預設行為
這個時候就需要自己手動實現mybatis的外掛程式攔截器。
引自官方對外掛程式的介紹:MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。
連結:http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins
mybatis可以攔截sqlSession執行下的4大行為的某些方法(按順序進行排列):Executor ->StatementHandler->ParameterHandler -> ResultSetHandler
4大行為可以參考blog:http://blog.csdn.net/ykzhen2015/article/details/50315027 外掛程式原理參考blog:http://blog.csdn.net/abcd898989/article/details/51244351
首先根據官網,我們來寫一個簡單的攔截器:
import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.plugin.*;import java.sql.Connection;import java.util.Properties;/** * @author yingzhi zhu * date 2018/1/19. */@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })public class PageInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("已攔截"); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { }} 運行程式,執行一次mapper操作,已生效。
但是這個例子和官網的有些許差異
@Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) @Signature裡面的內容不一樣,type是攔截的介面 method是介面中的方法 args 是方法中的參數。
這裡需要提出的一點,外掛程式攔截器只能攔截 如下4個介面中的某個方法 通過需求可以發現,需要實現的功能
1. 動態修改sql語句
2.動態執行尋找總頁數的sql語句
3. 動態擷取傳遞過來的參數
那麼攔截的方法中應該做到
1.可以在攔截的方法中擷取資料庫連接 並且單獨的進行資料庫操作
2.可以擷取原sql語句
3.動態修改原sql語句
4.可以擷取傳遞過來的方法中的參數
滿足條件並且最佳的是Statement介面中的prepare方法
在這個介面中方法參數內建資料庫連接對象,並且可以擷取BoundSql 和ParameterHander對象。mapper中的sql語句就是
儲存在BoundSql中的 ,mapper中參數相關資訊就是儲存在ParameterHander中的。
@Override public Object intercept(Invocation invocation) throws Throwable { //擷取資料庫連接 Connection connection = (Connection) invocation.getArgs()[0]; //擷取攔截的StatementHandler介面樣本對象 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); //擷取mybatis中儲存sql語句執行個體的對象 BoundSql boundSql = statementHandler.getBoundSql(); //擷取mybatis中儲存mapper中參數 執行個體的對象 ParameterHandler parameterHandler = statementHandler.getParameterHandler(); return invocation.proceed(); } intercept方法是在攔截方法執行之前進行一段額外的操作,傳遞過來的執行個體Invocation 是通過反射機制擷取的@Signature中
攔截的類,方法,參數的執行個體。最後需要通過invocation.procceed()方法執行原有方法,讓mybatis從新回到他的生命週期中預設
實現的方法。
通過以上的代碼 即可擷取到資料庫連接 sql語句 和參數的執行個體對象。 當然說到這裡這節還有最大的一個工作沒有做。不是所有的方法我們都能攔截,我們只攔截特定的方法:參數中帶有自訂的
Pageable對象的方法。
/** * 設定哪些Mybatis對象需要被該外掛程式攔截 * @param target Mybatis對象 * @return */ @Override public Object plugin(Object target) { //只攔截StatementHandler中並且參數中有Pageable對象的方法 if (target instanceof StatementHandler){ if (getPageable((StatementHandler) target) != null){ return Plugin.wrap(target, this); } } //允許存取 return target; } /** * 擷取方法中傳遞過來的Pageable對象參數 沒有返回NULL * @param statementHandler * @return */ private Pageable getPageable(StatementHandler statementHandler){ //取得傳遞過來的參數對象 Object o = statementHandler.getParameterHandler().getParameterObject(); //方式一 如果單只有一個參數 if (o instanceof Pageable){ return (Pageable) o; } //方式二 如果有多個參數 則會封裝成Map if (o instanceof HashMap){ for (Object value:((HashMap) o).values()){ if (value instanceof Pageable){ return (Pageable) value; } } } return null; }
3.為原SQL語句加上Limit
/** * 設定分頁sql語句 * @param boundSql * @param pageable */ private void setSqlStatement(BoundSql boundSql,Pageable pageable){ int start = (pageable.getPage()-1)*pageable.getSize(); int size = pageable.getSize(); String pagingSql = boundSql.getSql() + " Limit " + start + "," + size; //方法1:利用mybatis原生的方法 強行將sql語句寫入boundSql中 private final String sql 的欄位 MetaObject metaObject = MetaObject.forObject(boundSql, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory()); metaObject.setValue("sql",pagingSql);