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.reibang.com/p/7c7b8c2c985d

MyBatis 插件之攔截器(Interceptor)

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

Mybatis攔截器介紹

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哑姚,隨后出現(xiàn)的幾起案子芜茵,更是在濱河造成了極大的恐慌,老刑警劉巖绞佩,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件品山,死亡現(xiàn)場離奇詭異肘交,居然都是意外死亡扑馁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雄家,“玉大人,你說我怎么就攤上這事乱投。” “怎么了褐荷?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵嘹悼,是天一觀的道長杨伙。 經(jīng)常有香客問我,道長限匣,這世上最難降的妖魔是什么米死? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任究西,我火速辦了婚禮,結(jié)果婚禮上卤材,老公的妹妹穿的比我還像新娘扇丛。我一直安慰自己帆精,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堤器,像睡著了一般闸溃。 火紅的嫁衣襯著肌膚如雪拱撵。 梳的紋絲不亂的頭發(fā)上拴测,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天集索,我揣著相機與錄音务荆,去河邊找鬼穷遂。 笑死蚪黑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的抒寂。 我是一名探鬼主播掠剑,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼澡腾,長吁一口氣:“原來是場噩夢啊……” “哼糕珊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起澜公,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坟乾,失蹤者是張志新(化名)和其女友劉穎蝶防,沒想到半個月后间学,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡详羡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年水泉,在試婚紗的時候發(fā)現(xiàn)自己被綠了草则。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畔师。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牧牢,靈堂內(nèi)的尸體忽然破棺而出看锉,到底是詐尸還是另有隱情,我是刑警寧澤塔鳍,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布伯铣,位于F島的核電站,受9級特大地震影響轮纫,放射性物質(zhì)發(fā)生泄漏腔寡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一掌唾、第九天 我趴在偏房一處隱蔽的房頂上張望放前。 院中可真熱鬧,春花似錦糯彬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缝裤,卻和暖如春倘是,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背升敲。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工获茬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹏氧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像艇炎,于是被迫代替她去往敵國和親腺晾。 傳聞我的和親對象是個殘疾皇子归形,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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