在技术探讨之前,想提一个疑问,为啥要解读源码和核心技术呢?其实初级人员一般会觉得使用就行,没必要用那么多时间研读源码和流程。但是个人觉得解读源码有以下优点:
1、深入了解java,对自己相关整体知识的回归
2、拥抱设计模式,体验开源技术的设计思路
3、自主设计和数学逻辑思维提升
4、学会总结,提升自我
简介
mybatis插件是核心部分,在实际应用中有一点使用价值,比如物理分页查询,批量操作,数据库脚本监听和过滤,参数修改,日志监控等。mybatis插件时允许对点的映射语句进行拦截调用,默认情况下是方法拦截如下:
Executor(update,query,flushStatements,commit,rollback,getTransaction, close, isClosed)
ParameterHandler(getParameterObject,setParameters)
ResultSetHandler(handleResultSets,handleOutputParameters)
StatementHandler(prepare,parameterize,batch,update,query)
使用方式
通过@Intercepts注解使用生效,@Signature定义拦截属性,Signature属性解析
- type 拦截的类型,class类型
- method 拦截的方法,string类型
- args方法参数类型,class数组类型,因为防止重载
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatchExecutor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
Object returnObject = invocation.proceed(); // 也可以直接执行自己定义的处理器
// 后置处理
return returnObject;
}
}
常用两种配置,一种xml配置,一种java配置,不管是那种配置,都是configuration#addInterceptor方法加入拦截器
在xml中定义
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="**.**.MyBatchExecutor" />
</plugins>
或者居于注解中引用拦截器
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.addInterceptor(new MyBatchExecutor());
}
};
}
}
拦截器原理
interceptor装载
mybati其实是一种all-in-one,也就是所有数据配置归根于Configuration,具体初始化时居于xml解析存放到configuration中。另一种居于spring方式,构建工厂SqlSessionFactoryBean来构建SqlSessionFactory,比如spring boot的自动装配MybatisAutoConfiguration。
interceptor拦截
上面一图解答为啥说拦截器只针对四个地方拦截。此处是使用了动态代理。事先设置好所有拦截器,然后再调用方法前调用拦截器。
分别两步:
1、生成动态代理。
2、代理对象反射获取,方法判断后直接执行。
拦截器使用案例
这里编写批量操作拦截器案例
批量插件
我们都知道mybatis的Executor有三种,SimpleExecutor、ReuseExecutor、BatchExecutor,其实BatchExecutor已经实现的批量操作,但是不够好用。接下来我们定义物理批量操作,我们已map对应的id以Batch结束时,实现物理批量操作
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class BatchAdaptor extends BatchExecutor {
private Logger log = LoggerFactory.getLogger(BatchAdaptor.class);
public BatchAdaptor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
if (parameter == null) {
return super.update(ms, parameter);
}
Object params = null;
if (Map.class.isAssignableFrom(parameter.getClass())) { // DefaultSqlSession#wrapCollection
final Map<String, Object> paramMap = (Map<String, Object>) parameter;
if (paramMap.size() == 1) { // Map中array
if (paramMap.get("array") != null) {
params = paramMap.get("array");
} else {
params = paramMap.values().iterator().next();
}
} else if (paramMap.size() == 2) {
params = paramMap.get("collection");
}
} else if (parameter instanceof Iterable || parameter.getClass().isArray()) {
params = parameter;
} else {
params = Collections.singletonList(parameter);
}
final Iterable<?> paramIterable = toIterable(params);
try {
for (Object obj : paramIterable) {
super.update(ms, obj); // addBatch
}
List<BatchResult> batchResults = doFlushStatements(false); // executeBatch
if (batchResults == null || batchResults.size() == 0) {
return 0;
}
return resolveUpdateResult(batchResults);
} catch (Exception e) {
log.error("batch execute", e);
doFlushStatements(true);
/**
* 批量插入,则报异常
*/
if ("INSERT".equalsIgnoreCase(ms.getSqlCommandType().name())) {
throw e;
}
return 0;
}
}
/**
* 返回批量结果成功数
*
* @param batchResults
* @return
*/
private int resolveUpdateResult(final List<BatchResult> batchResults) {
int result = 0;
for (BatchResult batchResult : batchResults) {
int[] updateCounts = batchResult.getUpdateCounts();
if (updateCounts == null || updateCounts.length == 0) {
continue;
}
for (int updateCount : updateCounts) {
result += updateCount;
}
}
return result;
}
/**
* 统一转换
*
* @param params array或者Collections
* @return
*/
private Iterable<?> toIterable(final Object params) {
if (params == null) {
return Collections.emptyList();
}
Iterable<?> paramIterable;
if (params instanceof Iterable) {
paramIterable = (Iterable<?>) params;
} else if (params.getClass().isArray()) {
Object[] array = (Object[]) params;
paramIterable = Arrays.asList(array);
} else {
paramIterable = Collections.singletonList(params);
}
return paramIterable;
}
}
拦截器编写
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.sql.SQLException;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyBatchInterceptor implements Interceptor {
private Logger log = LoggerFactory.getLogger(MyBatchInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
//check argument
if (invocation.getArgs()[1] == null) {
return invocation.proceed();
}
final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 是否需要批处理标识
if (!mappedStatement.getId().endsWith("Batch")) {
return invocation.proceed();
}
// 若是批处理,则不做操作
if (BatchExecutor.class.isAssignableFrom(invocation.getTarget().getClass())) {
return invocation.proceed();
}
Executor executor = (Executor) invocation.getTarget();
// 创建批处理对象
final BatchExecutor batchExecutor = new BatchAdaptor(getConfiguration(executor), executor.getTransaction());
try {
return batchExecutor.update(mappedStatement, invocation.getArgs()[1]);
} catch (SQLException e) {
log.error("batch excute", e);
batchExecutor.flushStatements(true);
throw e;
}
}
/**
* 获取配置文件
*
* @param executor
* @return
*/
public Configuration getConfiguration(Executor executor) {
Field configField = ReflectionUtils.findField(executor.getClass(), "configuration");
if (configField == null) { // CachingExecutor
configField = ReflectionUtils.findField(executor.getClass(), "delegate");
if (!configField.isAccessible()) {
configField.setAccessible(true);
}
executor = (Executor) ReflectionUtils.getField(configField, executor);
configField = ReflectionUtils.findField(executor.getClass(), "configuration");
if (!configField.isAccessible()) {
configField.setAccessible(true);
}
}
// 获取配置文件
return (Configuration) ReflectionUtils.getField(configField, executor);
}
}
拦截器使用
@Configuration
public class MybatisConfig {
@Bean
public MyBatchInterceptor myBatchInterceptor() {
return new MyBatchInterceptor();
}
}
@Mapper
public interface UserTableMapper {
@Update("update user set name=#{name},msg=#{msg} where id=#{id}")
int updateBatch(List<UserTable> userTableList);
}
总结&反思
- JDK动态代理充分利用,如Plugin#wrap构建动态代理,同时学习代理模式
- java反射机制,获取属性并构造类ReflectionUtils
- spring boot的自动装配,AutoConfiguration的start设计
- 使用mybatis拦截器可以做一些批量操作,针对执行的四种点进行拦截特殊处理(parameterHandler,statementHandler,resultSetHandler,execute)
- mybatis的四种执行体Executor
- mybatis-plus版本更多关于批处理的增强版等之类。
参看文献
【1】mybatis3描述和源码