mybatis攔截器(上)


1. 攔截器注解

1. mybatis自定義攔截器實現步驟:

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

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

  1. Executor:攔截執行器的方法。
  2. ParameterHandler:攔截參數的處理。
  3. ResultHandler:攔截結果集的處理。
  4. StatementHandler:攔截Sql語法構建的處理。

1. 不同攔截類型執行順序:

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

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

 
image.png

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

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

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

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

3. 攔截器注解的作用:

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

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

4. 攔截器注解的規則:

具體規則如下:

@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 官方插件開發方式

@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(); //方法參數 // do something ...... 方法攔截前執行代碼塊 Object result = invocation.proceed(); // do something .......方法攔截后執行代碼塊 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> 

方法中獲取參數: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; } 

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

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

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

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

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

    /** * <p> * 獲得真正的處理對象,可能多層代理. * </p> */ 

2.2.3 intercept(Invocation invocation)方法

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

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

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

案例:將參數拼接到sql語句。

因為已經執行了ParameterHandler攔截器,故Statement對象已經是完全拼接好的SQL語句。

附錄

1. 完整版拼接sql語句

2. MappedStatement.class

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

  //節點中的id屬性加要命名空間 private String id; //直接從節點屬性中取 private Integer fetchSize; //直接從節點屬性中取 private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; //對應一條SQL語句 private SqlSource sqlSource; //每條語句都對就一個緩存,如果有的話。 private Cache cache; //這個已經過時了 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; //是否有內映射 private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;


作者:小胖學編程
鏈接:https://www.jianshu.com/p/0a72bb1f6a21
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM