mybatis(3)—自定義攔截器(上)基礎使用

mybatis自定義攔截器(一)基本使用
mybatis自定義攔截器(二)對象詳解

1. 攔截器注解

1. mybatis自定義攔截器實現(xiàn)步驟:

  1. 實現(xiàn)org.apache.ibatis.plugin.Interceptor接口。
  2. 添加攔截器注解org.apache.ibatis.plugin.Intercepts。
  3. 配置文件中添加攔截器。

2. 在mybatis中可被攔截的類型有四種(按照攔截順序):

  1. Executor:攔截執(zhí)行器的方法。
  2. ParameterHandler:攔截參數(shù)的處理。
  3. ResultHandler:攔截結(jié)果集的處理。
  4. StatementHandler:攔截Sql語法構(gòu)建的處理。

1. 不同攔截類型執(zhí)行順序:

com.galax.configuration.Aa#plugin打印攔截器對象順序.png

2. 多個插件攔截的順序?

image.png

需要注意的是,因為攔截器Aa和攔截器Bb均是攔截的StatementHandler對象,所以攔截器B在此獲取StatementHandler的時候,獲取的是代理對象。

攔截器對象的處理過程.png

3. 多個插件plugin()和intercept()方法的執(zhí)行順序

先執(zhí)行每個插件的plugin方法,若是@Intercepts注解標明需要攔截該對象,那么生成類型對象的代理對象。(即使該插件需要攔截該類型對象,但是依舊會執(zhí)行下一個插件的plugin方法)。知道執(zhí)行完畢所有的plugin方法。在執(zhí)行每個Intercept方法。

3. 攔截器注解的作用:

自定義攔截器必須使用mybatis提供的注解來聲明我們要攔截的類型對象。

Mybatis插件都要有Intercepts [in特賽婆斯]注解來指定要攔截哪個對象哪個方法。我們知道,Plugin.wrap方法會返回四大接口對象的代理對象,會攔截所有的方法。在代理對象執(zhí)行對應方法的時候,會調(diào)用InvocationHandler處理器的invoke方法。

4. 攔截器注解的規(guī)則:

具體規(guī)則如下:

@Intercepts({
    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
    @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
  1. @Intercepts:標識該類是一個攔截器;
  2. @Signature:指明自定義攔截器需要攔截哪一個類型,哪一個方法;
    2.1 type:對應四種類型中的一種;
    2.2 method:對應接口中的哪類方法(因為可能存在重載方法);
    2.3 args:對應哪一個方法;

5. 攔截器可攔截的方法:

攔截的類 攔截的方法
Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandler getParameterObject, setParameters
StatementHandler prepare, parameterize, batch, update, query
ResultSetHandler handleResultSets, handleOutputParameters

2. 攔截器方法

2.1 官方插件開發(fā)方式

@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class TestInterceptor implements Interceptor {
   public Object intercept(Invocation invocation) throws Throwable {
     Object target = invocation.getTarget(); //被代理對象
     Method method = invocation.getMethod(); //代理方法
     Object[] args = invocation.getArgs(); //方法參數(shù)
     // do something ...... 方法攔截前執(zhí)行代碼塊
     Object result = invocation.proceed();
     // do something .......方法攔截后執(zhí)行代碼塊
     return result;
   }
   public Object plugin(Object target) {
     return Plugin.wrap(target, this);
   }
}

2.2 攔截器的方法

public interface Interceptor {   
   Object intercept(Invocation invocation) throws Throwable;       
   Object plugin(Object target);    
   void setProperties(Properties properties);
}

2.2.1 setProperties方法

如果我們的攔截器需要一些變量對象,而且這個對象是支持可配置的。
類似于Spring中的@Value("${}")從application.properties文件中獲取。
使用方法:

<plugin interceptor="com.plugin.mybatis.MyInterceptor">
     <property name="username" value="xxx"/>
     <property name="password" value="xxx"/>
</plugin>

方法中獲取參數(shù):properties.getProperty("username");

問題:但是為什么不直接使用@Value("${}") 獲取變量?

解答:因為mybatis框架本身就是一個可以獨立使用的框架,沒有像Spring這種做了很多的依賴注入。

2.2.2 plugin方法

這個方法的作用是就是讓mybatis判斷,是否要進行攔截,然后做出決定是否生成一個代理。

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

需要注意的是:每經(jīng)過一個攔截器對象都會調(diào)用插件的plugin方法,也就是說,該方法會調(diào)用4次。根據(jù)@Intercepts注解來決定是否進行攔截處理。

問題1:Plugin.wrap(target, this)方法的作用?

解答:判斷是否攔截這個類型對象(根據(jù)@Intercepts注解決定),然后決定是返回一個代理對象還是返回原對象。

故我們在實現(xiàn)plugin方法時,要判斷一下目標類型,是本插件要攔截的對象時才執(zhí)行Plugin.wrap方法,否則的話,直接返回目標本身。

問題2:攔截器代理對象可能經(jīng)過多層代理,如何獲取到真實的攔截器對象?

    /**
     * <p>
     * 獲得真正的處理對象,可能多層代理.
     * </p>
     */
    @SuppressWarnings("unchecked")
    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }

2.2.3 intercept(Invocation invocation)方法

我們知道,mybatis只能攔截四種類型的對象。而intercept方法便是處理攔截到的對象。比如我們要攔截StatementHandler#query(Statement st,ResultHandler rh)方法,那么Invocation就是這個對象,Invocation中有三個參數(shù)。

  • target:StatementHandler;
  • method :query;
  • args[]:Statement st,ResultHandler rh

org.apache.ibatis.reflection.SystemMetaObject#forObject:方便的獲取對象中的值。

案例:將參數(shù)拼接到sql語句。

因為已經(jīng)執(zhí)行了ParameterHandler攔截器,故Statement對象已經(jīng)是完全拼接好的SQL語句。

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})
public class MybatisLogInterceptor implements Interceptor {

    private Properties properties;

    private static final Logger logger = LoggerFactory.getLogger(MybatisLogInterceptor.class);

    public Object intercept(Invocation invocation) throws Throwable {

        long start = 0L;
        String sqlId = "";
        BoundSql boundSql = null;
        Configuration configuration = null;
        Object returnValue = null;

        try {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            sqlId = mappedStatement.getId();
            if(sqlId.contains("History") || sqlId.contains("Tmp")){
                return invocation.proceed();
            }
            Object parameter = null;
            if (invocation.getArgs().length > 1) {
                parameter = invocation.getArgs()[1];
            }           
            boundSql = mappedStatement.getBoundSql(parameter);
            configuration = mappedStatement.getConfiguration();
            start = System.currentTimeMillis();
        } catch (Exception e) {
            logger.debug("Mybatis攔截器前置處理異常 原因:", e);
            logger.error("Mybatis攔截器前置處理異常 原因:" + e);
        }

        returnValue = invocation.proceed();

        try {
            long end = System.currentTimeMillis();
            long time = (end - start);
            String sql = getSql(configuration, boundSql, sqlId, time);
            // if (time >= Config.SQL_WARN_TIME) {
            // logger.warn(sql);
            // } else {
            // logger.info(sql);
            // }
        } catch (Exception e) {
            logger.debug("Mybatis攔截器后置處理異常 原因:", e);
            logger.error("Mybatis攔截器后置處理異常 原因:" + e);
        }

        return returnValue;
    }

    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder(100);
        str.append("【sqlId】").append(sqlId);
        str.append("【SQL耗時-").append(time).append("-毫秒】");
        str.append("【SQL】").append(sql);
        //logger.debug(SQLFormatter.format(str.toString()));
        logger.debug(str.toString());
        return str.toString();
    }

    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
            value = value.replaceAll("\\\\", "\\\\\\\\");
            value = value.replaceAll("\\$", "\\\\\\$");
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties0) {
        this.properties = properties0;
    }

}

2. MappedStatement.class

一個MappedStatement對象對應Mapper配置文件中的一個select/update/insert/delete節(jié)點,主要描述的是一條sql語句。其屬性為:

  //節(jié)點中的id屬性加要命名空間
  private String id;
  //直接從節(jié)點屬性中取
  private Integer fetchSize;
  //直接從節(jié)點屬性中取
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  //對應一條SQL語句
  private SqlSource sqlSource;
 
  //每條語句都對就一個緩存,如果有的話。
  private Cache cache;
  //這個已經(jīng)過時了
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  //SQL的類型,select/update/insert/detete
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  
  //是否有內(nèi)映射
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

文章參考

https://blog.csdn.net/Liu_York/article/details/88053053

http://www.itdecent.cn/p/7c7b8c2c985d

MyBatis 插件之攔截器(Interceptor)

Mybatis Plugin(攔截器)的開發(fā)

Mybatis攔截器介紹

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

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

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