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
是Mybatis
的内部执行器。它负责调用StatementHandler
操作数据库,并把结果集通过 ResultSetHandler
进行自动映射。
另外,它还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。
Executor
接口包含以下几个方法:
int update(MappedStatement ms, Object parameter) throws SQLException;
该方法会在所有的INSERT
、UPDATE
、DELETE
执行时被调用,因此如果想要拦截这3类操作,可以拦截该方法。接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})
<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})
<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})
List<BatchResult> flushStatements() throws SQLException;
该方法只在通过SqlSession
方法调用flushStatements
方法或执行的接口方法中带有@Flush
注解时才被调用,接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "flushStatements",
args = {})
void commit(boolean required) throws SQLException
该方法只在通过SqlSession
方法调用commit
方法时才被调用,接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "commit",
args = {boolean.class})
void rollback(boolean required) throws SQLException;
该方法只在通过SqlSession
方法调用rollback
方法时才被调用,接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "rollback",
args = {boolean.class})
Transaction getTransaction();
该方法只在通过SqlSession
方法获取数据库连接时才被调用,接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "getTransaction",
args = {})
void close(boolean forceRollback);
该方法只在延迟加载获取新的Executor
后才会被执行,接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "close",
args = {boolean.class})
boolean isClosed();
该方法只在延迟加载执行查询方法前被执行,接口方法对应的签名如下:
@Signature(
type= Executor.class,
method = "isClosed",
args = {})
ParameterHandler
是Mybatis
实现Sql
入参设置的对象。
这里,使用插件可以改变我们Sql
的参数默认设置。
ParameterHandler
接口包含以下两个方法:
Object getParameterObject();
该方法只在执行存储过程处理出参的时候被调用,接口方法对应的签名如下:
@Signature(
type= ParameterHandler.class,
method = "getParameterObject",
args = {})
void setParameters(PreparedStatement ps) throws SQLException;
该方法在所有数据库方法设置SQL参数时被调用,接口方法对应的签名如下:
@Signature(
type= ParameterHandler.class,
method = "setParameters",
args = {PreparedStatement.class})
ResultSetHandler
是Mybatis
把ResultSet
集合映射成POJO
的接口对象。
我们可以定义插件对Mybatis
的结果集自动映射进行修改。
ResultSetHandler
接口包含以下三个方法:
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
该方法会在除存储过程及返回值类型为Cursor
(org.apache.ibatis.cursor.Cursor
)以外的查询方法中被调用,接口方法对应的签名如下:
@Signature(
type= ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
该方法是在3.4.0
版本中新增加的,只会在返回值类型为Cursor
的查询方法中被调用,接口方法对应的签名如下:
@Signature(
type= ResultSetHandler.class,
method = "handleCursorResultSets",
args = {Statement.class})
void handleOutputParameters(CallableStatement cs) throws SQLException;
该方法只在使用存储过程处理出参时被调用,接口方法对应的签名如下:
@Signature(
type= ResultSetHandler.class,
method = "handleOutputParameters",
args = {CallableStatement.class})
ResultSetHandler
接口的第一个方法对于拦截处理MyBatis
的查询结果非常有用,并且由于这个接口被调用的位置在处理二级缓存之前,因此通过这种方式处理的结果可以执行二级缓存。
StatementHandler
是Mybatis
直接和数据库执行sql
脚本的对象。
另外它也实现了Mybatis
的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。
StatementHandler
接口包含以下几个方法:
Statement prepare(
Connection connection,
Integer transactionTimeout) throws SQLException;
该方法会在数据库执行前被调用,优先于当前接口中的其他方法而被执行,接口方法对应的签名如下:
@Signature(
type= StatementHandler.class,
method = "prepare",
args = {Collection.class, Integer.class})
void parameterize(Statement statement) throws SQLException;
该方法在prepare
方法之后执行,用于处理参数信息,接口方法对应的签名如下:
@Signature(
type= StatementHandler.class,
method = "parameterize",
args = {Statement.class})
int batch(Statement statement) throws SQLException;
在全局设置配置defaultExecutorType="BATCH"
时,执行数据操纵才会调用该方法,接口方法对应的签名如下:
@Signature(
type= StatementHandler.class,
method = "batch",
args = {Statement.class})
<E> List<E> query(
Statement statement,
ResultHandler resultHandler) throws SQLException;
执行SELECT
方法时调用,接口方法对应的签名如下:
@Signature(
type= StatementHandler.class,
method = "query",
args = {Statement.class, ResultHandler.class})
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
该方法是在3.4.0
版本中新增加的,只会在返回值类型为Cursor
的查询中被调用,接口方法对应的签名如下:
@Signature(
type= StatementHandler.class,
method = "queryCursor",
args = {Statement.class})