從零開始手寫 spring boot mybatis 分頁外掛程式

來源:互聯網
上載者:User

 先上一下最終的效果:


 









 通過傳入頁數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);

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.