1. 程式人生 > >MyBatis學習筆記9——MyBatis外掛開發

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>

在配置攔截器時,pluginInterceptor屬性為攔截器實現類的全限定名稱,如果需要引數,可以在plugin標籤內通過property標籤進行配置,配置後的引數在攔截器初始化時會通過setProperties方法傳遞給攔截器。在攔截器中可以很方便地通過Properties取得配置的引數值。

再看第二個方法plugin。這個方法的引數target就是攔截器要攔截的物件,該方法會在建立被攔截的介面實現類時被呼叫。該方法的實現很簡單,只需要呼叫MyBatis提供的Pluginorg.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;

該方法會在所有的INSERTUPDATEDELETE執行時被呼叫,因此如果想要攔截這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})