自己動手寫一個MyBatis自定義攔截器實現(xiàn)分頁

MyBatis是一款非常好用的持久層框架,它支持定制化SQL陈症、數(shù)據(jù)庫存儲過程及高級映射。MyBatis讓使用者避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis可采用XML和注解兩種方式配置和映射原生類型坷剧。接口和 Java 的 POJO。

一喊暖、攔截器Mybatis
而我們實現(xiàn)的分頁功能就是基于mybatis的插件模塊惫企,Mybatis為我們提供了Interceptor接口,通過實現(xiàn)該接口就可以定義我們自己的攔截器陵叽。我們先來看一下這個接口的定義:

public interface Interceptor {
  //是實現(xiàn)攔截邏輯的地方狞尔,內(nèi)部要通過invocation.proceed()顯式地推進責(zé)任鏈前進,也就是調(diào)用下一個攔截器攔截目標方法巩掺。
  Object intercept(Invocation invocation) throws Throwable;
  //就是用當前這個攔截器生成對目標target的代理
  Object plugin(Object target);
  //用于設(shè)置額外的參數(shù)偏序,參數(shù)配置在攔截器的Properties節(jié)點里
 void setProperties(Properties properties);
}

三、簡單案例實現(xiàn)
Demo采用技術(shù)SpringBoot+MyBatis
1胖替、pom.xml文件引入mybatis依賴

<dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
</dependency>
<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
</dependency>

2研儒、封裝類代碼實現(xiàn)

  1. 該類封裝了分頁查詢的頁碼和頁面大小,還有排序規(guī)独令。
/**
 * 通用分頁請求參數(shù)
 * @author wuyh
 */
public class PageRequest implements Serializable {
    private static final long serialVersionUID = -2464407342708149892L;
    /**
    *頁碼(從0開始)
    */
    private  int page;
    /**
     *每頁顯示數(shù)量
     */
    private  int size;
    /**
     *排序參數(shù)
     */
    private Sort sort;
    
    public PageRequest() {
        this(0, 10, (Sort)null);
    }
    public PageRequest(Sort sort) {
        this(0, 10, sort);
    }
    public PageRequest(int page, int size) {
        this(page, size, (Sort)null);
    }
    public PageRequest(int page, int size, Sort sort) {
        if (page < 0) {
            page = 0;
        }
        if (size < 0) {
            size = 0;
        }
        this.page = page;
        this.size = size;
        this.sort = sort;
    }
    
    public int getPage() {
        return this.page;
    }
    public int getSize() {
        return this.size;
    }
    
    public int getPageSize() {
        return this.size;
    }
    
    public int getPageNumber() {
        return this.page;
    }
    
    public long getOffset() {
        return this.page * this.size;
    }
    
    public boolean hasPrevious() {
        return this.page > 0;
    }
    
    public PageRequest next() {
        return new PageRequest(this.page + 1, this.size);
    }
    
    public PageRequest previousOrFirst() {
        return this.hasPrevious() ? new PageRequest(this.page - 1, this.size) : this;
    }
    public PageRequest first() {
        return new PageRequest(0, this.size);
    }
  
    public Sort getSort() {
        return this.sort;
    }
}
  1. 分頁結(jié)果集封裝
public class Page<T> implements Serializable {
    private static final long serialVersionUID = 1625981207349025919L;
    //查詢結(jié)果集
    private final List<T> content;
    //分頁參數(shù)
    private  PageRequest pageRequest;
    //總記錄數(shù)
    private  int total;
    public Page(List<T> content, PageRequest pageRequest, int total) {
        this.content = new ArrayList();
        if (null == content) {
            throw new IllegalArgumentException("Content must not be null!");
        } else {
            this.content.addAll(content);
            this.total = total;
            this.pageRequest = pageRequest;
        }
    }
    public Page(List<T> content, PageRequest pageRequest) {
        this(content, pageRequest, null == content ? 0 : content.size());
    }
    public Page(List<T> content) {
        this(content, (PageRequest)null, null == content ? 0 : content.size());
    }
    public int getNumberOfElements() {
        return this.content.size();
    }
    public int getTotalElements() {
        return this.total;
    }
    public List<T> getContent() {
        return Collections.unmodifiableList(this.content);
    }
    public boolean hasContent() {
        return !this.content.isEmpty();
    }
    public PageRequest getPageRequest() {
        return this.pageRequest;
    }
    public int getTotal() {
        return this.total;
    }
}
  1. 攔截器部分
    這里的只要思路是:
    建立一個Mybatis攔截器用于攔截Executor接口的query方法端朵,在攔截之后如果參數(shù)列表有分頁請求對象,我這里分頁重新拼接sql執(zhí)行實現(xiàn)自己的query方法邏輯燃箭,否則按原來方式執(zhí)行冲呢。
    @Intercepts 在實現(xiàn)Interceptor接口的類聲明,使該類PageInterceptor注冊成為攔截器。
package com.wuyh.demo.interceptor;
import com.wuyh.demo.utils.Page;
import com.wuyh.demo.utils.PageRequest;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
/**
 * @ClassName UserInterceptor
 * @Description: TODO
 * @Author wuyh
 * @Date 2022/2/10
 * @Version V1.0
**/
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(PageInterceptor.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("進入攔截器");
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        //獲取參數(shù)
        Object param = invocation.getArgs()[1];
        PageRequest pageRequest = this.getPageRequest(param);
        BoundSql boundSql = null;
        Object parameterObject = null;
        /**
         * 判斷參數(shù)列表是否有PageRequest來判斷是否需要進行分頁
         */
        if (pageRequest != null) {
            Object whereParam = getWhereParameter(param);
            boundSql = mappedStatement.getBoundSql(whereParam);
            //強轉(zhuǎn) 為了拿到分頁數(shù)據(jù)
            PageRequest pageVo = pageRequest;
            String sql = boundSql.getSql();
            //獲取相關(guān)配置
            Configuration config = mappedStatement.getConfiguration();
            Connection connection = config.getEnvironment().getDataSource().getConnection();
            //拼接查詢當前條件的sql的總條數(shù)
            String countSql = "select count(*) from (" + sql + ") a";
            PreparedStatement preparedStatement = connection.prepareStatement(countSql);
            BoundSql countBoundSql = new BoundSql(config, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBoundSql);
            parameterHandler.setParameters(preparedStatement);
            //執(zhí)行獲得總條數(shù)
            ResultSet rs = preparedStatement.executeQuery();
            int count = 0;
            if (rs.next()) {
                count = rs.getInt(1);
            }
            //拼接分頁sql
            String pageSql = sql + " limit " + pageVo.getOffset() + " , " + pageVo.getPageSize();
            //重新執(zhí)行新的sql
            doNewSql(invocation, pageSql);
            Object result = invocation.proceed();
            connection.close();
            //處理新的結(jié)構(gòu)
            Page<?> page = new Page((List)result, pageVo, count);
            List<Page> returnResultList = new ArrayList<>();
            returnResultList.add(page);
            return returnResultList;
        }
        return invocation.proceed();
    }
    private void doNewSql(Invocation invocation, String sql){
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = getWhereParameter(args[1]);
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
    }
    /**
     * 獲取新的MappedStatement
     * @param ms
     * @param newSqlSource
     * @return
     */
    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.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());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }
    @Override
    public Object plugin(Object o) {
        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }
    @Override
    public void setProperties(Properties properties) {
    }
    /**
     * 新的SqlSource需要實現(xiàn)
     */
    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
    /**
    * 從參數(shù)列表返回PageRequest
    */
    public PageRequest getPageRequest(Object paramMap) {
        if (paramMap == null) {
            return null;
        } else if (PageRequest.class.isAssignableFrom(paramMap.getClass())) {
            return (PageRequest)paramMap;
        } else {
            if (paramMap instanceof ParamMap) {
                ParamMap map = (ParamMap)paramMap;
                Iterator iterator = map.entrySet().iterator();
                while(iterator.hasNext()) {
                    Map.Entry entry = (Map.Entry)iterator.next();
                    Object obj = entry.getValue();
                    if (obj != null && PageRequest.class.isAssignableFrom(obj.getClass())) {
                        return (PageRequest)obj;
                    }
                }
            }
            return null;
        }
    }
    
    private Object getWhereParameter(Object obj) {
        if (obj instanceof ParamMap) {
            ParamMap paramMap = (ParamMap)obj;
            if (paramMap.size() == 4) {
                Iterator iterator = paramMap.entrySet().iterator();
                while(iterator.hasNext()) {
                    Map.Entry var4 = (Map.Entry)iterator.next();
                    Object var5 = var4.getValue();
                    if (Sort.class.isAssignableFrom(var5.getClass()) || PageRequest.class.isAssignableFrom(var5.getClass())) {
                        return paramMap.get("param1");
                    }
                }
            }
        }
        return obj;
    }
}

4)業(yè)務(wù)代碼

@Mapper
public interface SysUserMapper  {
   
   Page<SysUser> selectUserPage(SysUser user, PageRequest pageRequest);
   int addUser(SysUser user);
   void deleteUser(Long userId);
}
@Service
public class UserService {
    @Autowired
    SysUserMapper userMapper;
    public Page<SysUser> selectUserPage(){
        SysUser sysUser = new SysUser();
        return userMapper.selectUserPage(sysUser, new PageRequest(0, 10));
    }
    
    public void add(Long userId, String userName, String passWord) {
        userMapper.deleteUser(userId);
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setUserName(userName);
        sysUser.setPassWord(passWord);
        userMapper.addUser(sysUser);
    }
}
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("getUser")
    public String GetUser(){
        userService.add(11L,"pageOne", "Admin1");
        userService.add(12L,"pageTwo", "Admin1");
        return userService.selectUserPage().getContent().toString();
    }
}
  1. Demo測試結(jié)果


    image

四招狸、 小結(jié)
Executor是sql語句的執(zhí)行器敬拓,Executor通過配置對象創(chuàng)建StatementHandler邻薯,繼而得到了StatementHandler,StatementHandler是整個數(shù)據(jù)庫訪問過程的控制關(guān)鍵乘凸,它的內(nèi)部持有ParameterHandler厕诡,因此StatementHandler可以通過后者來處理參數(shù)。在StatementHandler處理參數(shù)的過程中會通過參數(shù)類型來找到對應(yīng)的typeHandler來處理參數(shù)营勤,整個過程中Statement對象都作為參數(shù)在傳遞木人,到了typeHandler他會調(diào)用Statement的setInt來設(shè)置值,其實整個過程中Statement對象都在傳遞冀偶,Mybatis通過封裝醒第,但是還是在使用JDBC的API。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末进鸠,一起剝皮案震驚了整個濱河市稠曼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌客年,老刑警劉巖霞幅,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異量瓜,居然都是意外死亡司恳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門绍傲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扔傅,“玉大人,你說我怎么就攤上這事烫饼×匀” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵杠纵,是天一觀的道長荠耽。 經(jīng)常有香客問我,道長比藻,這世上最難降的妖魔是什么铝量? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮银亲,結(jié)果婚禮上慢叨,老公的妹妹穿的比我還像新娘。我一直安慰自己群凶,他們只是感情好插爹,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布哄辣。 她就那樣靜靜地躺著请梢,像睡著了一般赠尾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毅弧,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天气嫁,我揣著相機與錄音,去河邊找鬼够坐。 笑死寸宵,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的元咙。 我是一名探鬼主播梯影,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼庶香!你這毒婦竟也來了甲棍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赶掖,失蹤者是張志新(化名)和其女友劉穎感猛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奢赂,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡陪白,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了膳灶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咱士。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖轧钓,靈堂內(nèi)的尸體忽然破棺而出司致,到底是詐尸還是另有隱情,我是刑警寧澤聋迎,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布脂矫,位于F島的核電站,受9級特大地震影響霉晕,放射性物質(zhì)發(fā)生泄漏庭再。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一牺堰、第九天 我趴在偏房一處隱蔽的房頂上張望拄轻。 院中可真熱鬧,春花似錦伟葫、人聲如沸恨搓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斧抱。三九已至常拓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辉浦,已是汗流浹背弄抬。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宪郊,地道東北人掂恕。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像弛槐,于是被迫代替她去往敵國和親懊亡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容