前言
MyBatis开放用户实现自己的插件,从而对整个调用过程进行个性化扩展。
这是MyBatis整个调用流程的主要参与者。
我们可以对其中的一些过程进行拦截,添加自己的功能,比如重写Sql添加分页参数。
拦截的接口
MyBatis允许拦截的接口如下
Executor
public interface Executor { ResultHandler NO_RESULT_HANDLER = null; int update(MappedStatement var1, Object var2) throws SQLException; <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException; <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException; <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException; List<BatchResult> flushStatements() throws SQLException; void commit(boolean var1) throws SQLException; void rollback(boolean var1) throws SQLException; CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4); boolean isCached(MappedStatement var1, CacheKey var2); void clearLocalCache(); void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5); Transaction getTransaction(); void close(boolean var1); boolean isClosed(); void setExecutorWrapper(Executor var1); }
ParameterHandler
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement var1) throws SQLException; }
ResultSetHandler
public interface ResultSetHandler { <E> List<E> handleResultSets(Statement var1) throws SQLException; <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException; void handleOutputParameters(CallableStatement var1) throws SQLException; }
StatementHandler
public interface StatementHandler { Statement prepare(Connection var1, Integer var2) throws SQLException; void parameterize(Statement var1) throws SQLException; void batch(Statement var1) throws SQLException; int update(Statement var1) throws SQLException; <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException; <E> Cursor<E> queryCursor(Statement var1) throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }
只要拦截器定义了拦截的接口和方法,后续调用该方法时,将会被拦截。
拦截器实现
如果要实现自己的拦截器,需要实现接口Interceptor
@Component @Slf4j @Intercepts(@Signature(type = Executor.class, method =\"update\", args ={MappedStatement.class,Object.class} )) public class MyIntercetor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { log.info(\"MyIntercetor ...\"); Object result = invocation.proceed(); log.info(\"result = \" + result); return result; } @Override public Object plugin(Object o) { return Plugin.wrap(o,this); } @Override public void setProperties(Properties properties) { } }
1. 拦截方法配置
@Intercepts(@Signature(type = Executor.class, method =\"update\", args ={MappedStatement.class,Object.class} ))
我们知道Java中方法的签名包括所在的类,方法名称,入参。
@Signature定义方法签名
type:拦截的接口,为上节定义的四个接口
method:拦截的接口方法
args:参数类型列表,需要和方法中定义的顺序一致。
2. intercept(Invocation invocation)
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return this.target;
}
public Method getMethod() {
return this.method;
}
public Object[] getArgs() {
return this.args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return this.method.invoke(this.target, this.args);
}
}
通过Invocation可以获取到被拦截的方法的调用对象,方法,参数。
proceed()用于继续执行并获得最终的结果。
这里使用了设计模式中的责任链模式。
3.这里不能返回null。
@Override public Object plugin(Object o) { return Plugin.wrap(o,this); }
4.拦截器在执行前输出\"MyIntercetor ...\",在数据库操作返回后输出\"result =xxx\"
log.info(\"MyIntercetor ...\"); Object result = invocation.proceed(); log.info(\"result = \" + result);
插件实现完成!
测试
在Spring中引入很简单。
第一种方式:
创建拦截器的bean
@Slf4j @Configuration public class IntercetorConfiguration { @Bean public MyIntercetor myIntercetor(){ return new MyIntercetor(); } }
第二种方式
手动往Configuration中添加拦截器。
@Slf4j @Configuration public class IntercetorConfiguration { @Autowired private List<SqlSessionFactory> sqlSessionFactoryList; @PostConstruct public void addPageInterceptor() { MyIntercetor interceptor = new MyIntercetor(); Iterator var3 = this.sqlSessionFactoryList.iterator(); while(var3.hasNext()) { SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var3.next(); sqlSessionFactory.getConfiguration().addInterceptor(interceptor); } } }
由于上面定义的拦截器是拦截Executor的update方法,所以在执行insert,update,delete的操作时,将会被拦截。
本例子使用insert来测试。具体代码查看:GitHub
2019-06-10 16:08:03.109 INFO 20410 --- [nio-8110-exec-1] c.m.user.dao.intercetor.MyIntercetor : MyIntercetor ... 2019-06-10 16:08:03.166 INFO 20410 --- [nio-8110-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited 2019-06-10 16:08:03.267 DEBUG 20410 --- [nio-8110-exec-1] o.m.s.t.SpringManagedTransaction : JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5cb1c36e] will not be managed by Spring 2019-06-10 16:08:03.274 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.dao.mapper.UserMapper.insertList : ==> Preparing: insert into user (name) values (?) , (?) , (?) 2019-06-10 16:08:03.307 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.dao.mapper.UserMapper.insertList : ==> Parameters: name:58(String), name:64(String), name:69(String) 2019-06-10 16:08:03.355 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.dao.mapper.UserMapper.insertList : <== Updates: 3 2019-06-10 16:08:03.358 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.d.m.U.insertList!selectKey : ==> Preparing: SELECT LAST_INSERT_ID() 2019-06-10 16:08:03.358 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.d.m.U.insertList!selectKey : ==> Parameters: 2019-06-10 16:08:03.380 DEBUG 20410 --- [nio-8110-exec-1] c.m.u.d.m.U.insertList!selectKey : <== Total: 1
2019-06-10 16:08:03.381 INFO 20410 --- [nio-8110-exec-1] c.m.user.dao.intercetor.MyIntercetor : result = 3
可以看到拦截器被调用了。
如果需要对查询进行拦截,可以拦截以下方法,比如通过MappedStatement修改sql语句。
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException; <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
结束!!!!!