MyBatis學習筆記9——MyBatis外掛開發
Mybatis
允許在對映語句執行過程中的某一點進行攔截呼叫。
預設情況下,Mybatis
允許使用外掛來攔截的介面和方法包括以下幾個:
序號 | 介面 | 方法 | 描述 |
---|---|---|---|
1 | Executor | update、query、flushStatements、commit、rollback、getTransaction、close、isClosed | 攔截執行器的方法 |
2 | ParameterHandler | getParameterObject、setParameters | 攔截引數的處理 |
3 | ResultSetHandler | handleResultSets、handleCursorResultSets、handleOutputParameters | 攔截結果集的處理 |
4 | StatementHandler | prepare、parameterize、batch、update、query | 攔截Sql語法構建的處理 |
這些類中方法的細節可以通過檢視每個方法的簽名來發現,或者直接檢視 MyBatis
的發行包中的原始碼。 假設你想做的不僅僅是監控方法的呼叫,那麼你應該很好的瞭解正在重寫的方法的行為。 因為如果在試圖修改或重寫已有方法的行為的時候,你很可能在破壞 MyBatis
的核心模組。 這些都是底層的類和方法,所以使用外掛的時候要特別當心。
攔截器介面介紹
MyBatis
外掛可以用來實現攔截器介面 Interceptor
(org.apache.ibatis.plugin.Interceptor
先來看攔截器介面,瞭解該介面的每一個方法的作用和用法。Interceptor
介面程式碼如下:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target) ;
void setProperties(Properties properties);
}
首先是 setProperties
方法,這個方法用來傳遞外掛的引數,可以通過引數來改變外掛的行為。引數值傳遞原理如下:
在mybatis-config.xml
中,一般情況下,攔截器的配置如下:
<plugins>
<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
<property name="propl" value ="valuel"/>
<property name="prop2" value ="value2"/>
</plugin>
</plugins>
在配置攔截器時,plugin
的Interceptor
屬性為攔截器實現類的全限定名稱,如果需要引數,可以在plugin
標籤內通過property
標籤進行配置,配置後的引數在攔截器初始化時會通過setProperties
方法傳遞給攔截器。在攔截器中可以很方便地通過Properties
取得配置的引數值。
再看第二個方法plugin
。這個方法的引數target
就是攔截器要攔截的物件,該方法會在建立被攔截的介面實現類時被呼叫。該方法的實現很簡單,只需要呼叫MyBatis
提供的Plugin
(org.apache.ibatis.plugin.Plugin
)類的wrap
靜態方法就可以通過Java
的動態代理攔截目標物件。這個介面方法通常的實現程式碼如下:
@Override
public Object plugin(Object target){
return Plugin.wrap(target,this);
}
Plugin.wrap
方法會自動判斷攔截器的簽名和被攔截物件的介面是否匹配,只有匹配的情況下才會使用動態代理攔截目標物件,因此在上面的實現方法中不必做額外的邏輯判斷。
最後一個intercept
方法是MyBatis
執行時要執行的攔截方法。通過該方法的引數invocation
可以得到很多有用的資訊,該引數的常用方法如下:
@Override
public Object intercept(Invocation invocation) throws Throwable{
Object target=invocation.getTarget();
Method method=invocation.getMethod();
Object[] args=invocation.getArgs();
Object result=invocation.proceed();
return result;
}
使用getTarget()
方法可以獲取當前被攔截的物件,使用getMethod()
可以獲取當前被攔截的方法,使用getArgs()
方法可以返回被攔截方法中的引數。通過呼叫invocation.proceed();
可以執行被攔截物件真正的方法,proceed()
方法實際上執行了method.invoke(target,args)
方法上面的程式碼中沒有做任何特殊處理,直接返回了執行的結果。
當配置多個攔截器時,MyBatis
會遍歷所有攔截器,按順序執行攔截器的plugin
方法,被攔截的物件就會被層層代理。在執行攔截物件的方法時,會一層層地呼叫攔截器,攔截器通過invocation.proceed()
呼叫下一層的方法,直到真正的方法被執行。方法執行的結果會從最裡面開始向外一層層返回,所以如果存在按順序配置的A、B、C
三個簽名相同的攔截器,MyBatis
會按照C>B>A>target.proceed()>A>B>C
的順序執行。如果A、B、C
簽名不同,就會按照**MyBatis
攔截物件的邏輯**執行。
攔截器簽名介紹
除了需要實現攔截器介面外,還需要給實現類配置以下的攔截器註解。
@Intercepts(org.apache.ibatis.plugin.Intercepts)
和簽名註解@Signature(org.apache.ibatis.plugin.Signature)
,這兩個註解用來配置攔截器要攔截的介面的方法。
@Intercepts
註解中的屬性是一個@Signature
(簽名)陣列,可以在同一個攔截器中同時攔截不同的介面和方法。
以攔截ResultSetHandler
介面的handleResultSets
方法為例,配置簽名如下:
@Intercepts({
@Signature(
type= ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})
})
public class ResultSetInterceptor implements Interceptor
@Signature
註解包含以下三個屬性:
type
:設定攔截的介面,可選值是前面提到的4個介面。method
:設定攔截介面中的方法名,可選值是前面4個介面對應的方法,需要和介面匹配。args
:設定攔截方法的引數型別陣列,通過方法名和引數型別可以確定唯一一個方法。
由於MyBatis
程式碼具體實現的原因,可以被攔截的4個介面中的方法並不是都可以被攔截的。下面將針對這4種介面,講可以被攔截的方法以及方法被呼叫的位置和對應的攔截器簽名依次列舉出來。
Executor介面
Executor
介面包含以下幾個方法:
update
int update(MappedStatement ms, Object parameter) throws SQLException;
該方法會在所有的INSERT
、UPDATE
、DELETE
執行時被呼叫,因此如果想要攔截這3類操作,可以攔截該方法。介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})
query
<E> List<E> query(
MappedStatement ms,
Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException;
該方法會在所有SELECT
查詢方法執行時被呼叫。通過這個介面引數可以獲取很多有用的資訊,因此這是最常被攔截的一個方法。使用該方法需要注意的是,雖然介面中還有一個引數更多的同名介面,但由於MyBatis
的設計原因,這個引數多的介面不能被攔截。介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
queryCursor
<E> Cursor<E> queryCursor(
MappedStatement ms,
Object parameter,
RowBounds rowBounds) throws SQLException;
該方法只有在查詢的返回值型別為Cursor
時被呼叫。介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "queryCursor",
args = {MappedStatement.class, Object.class,
RowBounds.class})
flushStatements
List<BatchResult> flushStatements() throws SQLException;
該方法只在通過SqlSession
方法呼叫flushStatements
方法或執行的介面方法中帶有@Flush
註解時才被呼叫,介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "flushStatements",
args = {})
commit
void commit(boolean required) throws SQLException
該方法只在通過SqlSession
方法呼叫commit
方法時才被呼叫,介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "commit",
args = {boolean.class})
rollback
void rollback(boolean required) throws SQLException;
該方法只在通過SqlSession
方法呼叫rollback
方法時才被呼叫,介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "rollback",
args = {boolean.class})
getTransaction
Transaction getTransaction();
該方法只在通過SqlSession
方法獲取資料庫連線時才被呼叫,介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "getTransaction",
args = {})
close
void close(boolean forceRollback);
該方法只在延遲載入獲取新的Executor
後才會被執行,介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "close",
args = {boolean.class})
isClosed
boolean isClosed();
該方法只在延遲載入執行查詢方法前被執行,介面方法對應的簽名如下:
@Signature(
type= Executor.class,
method = "isClosed",
args = {})
ParameterHandler介面
ParameterHandler
介面包含以下兩個方法:
getParameterObject
Object getParameterObject();
該方法只在執行儲存過程處理出參的時候被呼叫,介面方法對應的簽名如下:
@Signature(
type= ParameterHandler.class,
method = "getParameterObject",
args = {})
setParameters
void setParameters(PreparedStatement ps) throws SQLException;
該方法在所有資料庫方法設定SQL引數時被呼叫,介面方法對應的簽名如下:
@Signature(
type= ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class})
ResultSetHandler介面
ResultSetHandler
介面包含以下三個方法:
handleResultSets
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
該方法會在除儲存過程及返回值型別為Cursor<T>
(org.apache.ibatis.cursor.Cursor<T>
)以外的查詢方法中被呼叫,介面方法對應的簽名如下:
@Signature(
type= ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})
handleCursorResultSets
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
該方法是在3.4.0
版本中新增加的,只會在返回值型別為Cursor<T>
的查詢方法中被呼叫,介面方法對應的簽名如下:
@Signature(
type= ResultSetHandler.class,
method = "handleCursorResultSets",
args = {Statement.class})
handleOutputParameters
void handleOutputParameters(CallableStatement cs) throws SQLException;
該方法只在使用儲存過程處理出參時被呼叫,介面方法對應的簽名如下:
@Signature(
type= ResultSetHandler.class,
method = "handleOutputParameters",
args = {CallableStatement.class})
ResultSetHandler
介面的第一個方法對於攔截處理MyBatis
的查詢結果非常有用,並且由於這個介面被呼叫的位置在處理二級快取之前,因此通過這種方式處理的結果可以執行二級快取。
StatementHandler介面
StatementHandler
介面包含以下幾個方法:
prepare
Statement prepare(
Connection connection,
Integer transactionTimeout) throws SQLException;
該方法會在資料庫執行前被呼叫,優先於當前介面中的其他方法而被執行,介面方法對應的簽名如下:
@Signature(
type= StatementHandler.class,
method = "prepare",
args = {Collection.class, Integer.class})
parameterize
void parameterize(Statement statement) throws SQLException;
該方法在prepare
方法之後執行,用於處理引數資訊,介面方法對應的簽名如下:
@Signature(
type= StatementHandler.class,
method = "parameterize",
args = {Statement.class})
batch
int batch(Statement statement) throws SQLException;
在全域性設定配置defaultExecutorType="BATCH"
時,執行資料操縱才會呼叫該方法,介面方法對應的簽名如下:
@Signature(
type= StatementHandler.class,
method = "batch",
args = {Statement.class})
query
<E> List<E> query(
Statement statement,
ResultHandler resultHandler) throws SQLException;
執行SELECT
方法時呼叫,介面方法對應的簽名如下:
@Signature(
type= StatementHandler.class,
method = "query",
args = {Statement.class, ResultHandler.class})
queryCursor
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
該方法是在3.4.0
版本中新增加的,只會在返回值型別為Cursor<T>
的查詢中被呼叫,介面方法對應的簽名如下:
@Signature(
type= StatementHandler.class,
method = "queryCursor",
args = {Statement.class})