自定義mybatis攔截器實(shí)現(xiàn)分頁(yè)

原理:

mybatis提供了攔截器功能,我們可以對(duì)Executor,StatementHandler,ParameterHandler,ResultSetHandler進(jìn)行攔截,也就是代理。

實(shí)現(xiàn):

分頁(yè)需要提供兩個(gè)數(shù)據(jù),一是偏移量,行數(shù)。在mysql數(shù)據(jù)庫(kù)中我們通過(guò)limit offset,rows實(shí)現(xiàn)分頁(yè)。
返回的結(jié)果,我們需要總數(shù),總頁(yè)數(shù),當(dāng)前頁(yè)數(shù),結(jié)果集來(lái)映射我們的前端顯示。
我們通過(guò)攔截Executor來(lái)實(shí)現(xiàn),并且制定攔截的方法是query方法。

  1. 第一步:區(qū)分出分頁(yè)方法,怎么實(shí)現(xiàn)在不改變?nèi)魏未a的情況下將分頁(yè)區(qū)別出來(lái),首先,我們肯定需要知道哪些方法是分頁(yè),我們通過(guò)定義統(tǒng)一的方法后綴名來(lái)區(qū)分。
  2. 第二步:查詢(xún)出總數(shù),我們需要在原來(lái)sql的基礎(chǔ)上查詢(xún)出總數(shù)是多少,在這里我們通過(guò)映射對(duì)象,調(diào)用Executor的query方法來(lái)獲得。
  3. 第三步:改造sql,變成分頁(yè)的sql語(yǔ)句。
  4. 第四步:執(zhí)行語(yǔ)句。
    在這里有些地方并沒(méi)有提供直接的修改方法,所以只能通過(guò)替換的形式,代碼冗余。
原理理解

在這其中有幾個(gè)關(guān)鍵的類(lèi)

  1. MappedStatement
  2. BoundSql
  3. Executor
MappedStatement

這個(gè)就是我們mapper文件中對(duì)應(yīng)的一個(gè)select/update/delete節(jié)點(diǎn)。

public final class MappedStatement {
   private String resource;//mapper配置文件名,如:UserMapper.xml
   private Configuration configuration;//全局配置
   private String id;//節(jié)點(diǎn)的id屬性加命名空間,如:com.lucky.mybatis.dao.UserMapper.selectByExample
   private Integer fetchSize;
   private Integer timeout;//超時(shí)時(shí)間
   private StatementType statementType;//操作SQL的對(duì)象的類(lèi)型
   private ResultSetType resultSetType;//結(jié)果類(lèi)型
   private SqlSource sqlSource;//sql語(yǔ)句
   private Cache cache;//緩存
   private ParameterMap parameterMap;
   private List<ResultMap> resultMaps;
   private boolean flushCacheRequired;
   private boolean useCache;//是否使用緩存,默認(rèn)為true
   private boolean resultOrdered;//結(jié)果是否排序
   private SqlCommandType sqlCommandType;//sql語(yǔ)句的類(lèi)型,如select、update、delete、insert
   private KeyGenerator keyGenerator;
   private String[] keyProperties;
   private String[] keyColumns;
   private boolean hasNestedResultMaps;
   private String databaseId;//數(shù)據(jù)庫(kù)ID
   private Log statementLog;
   private LanguageDriver lang;
   private String[] resultSets;

通過(guò)mapperStatement我們可以獲得sql,方法名,參數(shù),結(jié)果類(lèi)型,這些都是我們后面需要用到的。

BoundSql

BoundSql保存了sql執(zhí)行sql語(yǔ)句所需要的所有參數(shù),sql語(yǔ)句,傳入的參數(shù)

public class BoundSql {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;
Executor

執(zhí)行器,所有的語(yǔ)句都通過(guò)這個(gè)執(zhí)行器執(zhí)行。我們下面主要的攔截對(duì)象,下面是我們要攔截的方法。

 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
思路

有了上面的對(duì)象,我們也可以構(gòu)建自己的查詢(xún),對(duì)于分頁(yè)查詢(xún),我們說(shuō)先需要獲得總數(shù)量,這里我們需要在原有的基礎(chǔ)上構(gòu)建一個(gè)新的MappedStatement,修改sql和返回結(jié)果類(lèi)型。對(duì)于分頁(yè)查詢(xún),我們只需要在原來(lái)的基礎(chǔ)上修改sql,但是MappedStatement并沒(méi)喲支持直接修改sql的方法,我們只能構(gòu)建新的MappedStatement替換原來(lái)的MappedStatement。

代碼實(shí)現(xiàn):

繼承Interceptor接口。添加@Interceptor注解。在@Signature 注解中指定攔截的類(lèi)型,方法,方法參數(shù)。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
    Signature[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
    Class<?> type();

    String method();

    Class<?>[] args();
}

實(shí)現(xiàn)接口的三個(gè)方法。

public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return null;
    }

    @Override
    public Object plugin(Object o) {
        return null;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

setProperties方法,有我們自定義提供方法的后綴,和頁(yè)大小。

  public void setProperties(Properties properties)  {
        Integer o = Integer.valueOf((String) properties.get("pager.pageSize"));
        if(o==null){
            try {
                throw  new PageSizeNullException("pageSize can not be null");
            } catch (PageSizeNullException e) {
                e.printStackTrace();
            }
        }
        pageSize=o;
        String o1 = (String) properties.get("pager.pageFlag");
        if(o1==null){
            try {
                throw  new PageFlagNullException("pageFlag can not be null.");
            } catch (PageFlagNullException e) {
                e.printStackTrace();
            }
        }
        PAGE_FLAG=o1;

    }

intercept方法。參數(shù)Invocation,我們通過(guò)斷點(diǎn)來(lái)看一下。target是Executor執(zhí)行器,我們?cè)赼rgs中可以獲得MappedStatement映射對(duì)象,UserPager參數(shù),RowBounds。


image.png

第一步:獲得總數(shù)。

  /**
     * 構(gòu)造count查詢(xún)語(yǔ)句,創(chuàng)建新的boundSql,創(chuàng)建新的mappedStatement,再通過(guò)executor執(zhí)行查詢(xún)
     * @param executor
     * @param mappedStatement
     * @param parameter
     * @param boundSql
     * @param resultHandler
     * @return
     * @throws SQLException
     */
 public Long getTotalCount(Executor executor,MappedStatement mappedStatement,Object parameter,BoundSql boundSql,ResultHandler resultHandler) throws SQLException {
       String sql = boundSql.getSql();
        StringBuffer stringBuffer  = new StringBuffer();
        stringBuffer.append("select count(1) from (").append(sql).append(") as temp");
        BoundSql newboundSql = new BoundSql(mappedStatement.getConfiguration(),stringBuffer.toString(),boundSql.getParameterMappings(),parameter);
        MappedStatement mappedStatement1 = buildCountMappedStatement(mappedStatement, mappedStatement.getId()+COUNT_FLAG);
        CacheKey cacheKey = executor.createCacheKey(mappedStatement1, parameter, RowBounds.DEFAULT, boundSql);
        List query = executor.query(mappedStatement1, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, newboundSql);
        return (Long)query.get(0);
    }
 /**
     * 創(chuàng)建新的mappedStatement,添加結(jié)果映射
     * @param mappedStatement
     * @param id
     * @return
     */
    public MappedStatement buildCountMappedStatement(MappedStatement mappedStatement,String id){
            MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),id,mappedStatement.getSqlSource(),mappedStatement.getSqlCommandType());

            List<ResultMap> resultMapList = new ArrayList<>();
            ResultMap.Builder resultMap =  new ResultMap.Builder(mappedStatement.getConfiguration(),id,Long.class,DEFAULT_LIST_RESULTMAPPING);
            resultMapList.add(resultMap.build());
            builder.resultMaps(resultMapList);
            return builder.build();
        }

獲得總數(shù)之后,開(kāi)始構(gòu)建分頁(yè)查詢(xún),我們只需要在原來(lái)的基礎(chǔ)之上修改sql就行了,但是并沒(méi)有可以直接修改sql的方法,所以我們只能通過(guò)替換mappedStatement的方式。mybatis提供了一個(gè)類(lèi)似反射的工具M(jìn)etaObject。

 /**
     * 利用mybatis提供的反射工具,將sql注入到mappedStatement中去
     * @param invocation
     * @param sql
     */
    public void resetSql2Invocation(Invocation invocation,String sql){
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatements = (MappedStatement) args[0];
        Object parameter= args[1];
        MappedStatement mappedStatement = buildMappedStatement(mappedStatements, mappedStatements.getBoundSql(parameter));
        MetaObject metaObject = MetaObject.forObject(mappedStatement, OBJECT_FACTORY, OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        metaObject.setValue("sqlSource.boundSql.sql",sql);
        args[0]=mappedStatement;
    }

    /**
     * 構(gòu)建新的mappedStatement
     * @param mappedStatement
     * @param boundSql
     * @return
     */
    public MappedStatement buildMappedStatement(MappedStatement mappedStatement,BoundSql boundSql){
        MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),mappedStatement.getId(),new BoundSqlSource(boundSql),mappedStatement.getSqlCommandType());
       setProperties(builder,mappedStatement);
        return builder.build();
    }

至此,完成了分頁(yè)的實(shí)現(xiàn),我們看一下結(jié)果。


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容