io.github.egd.prodigal.mybatis.batch.plugins.BatchInsertInterceptor Maven / Gradle / Ivy
package io.github.egd.prodigal.mybatis.batch.plugins;
import io.github.egd.prodigal.mybatis.batch.annotations.BatchInsert;
import io.github.egd.prodigal.mybatis.batch.core.BatchInsertContext;
import io.github.egd.prodigal.mybatis.batch.core.BatchSqlSessionBuilder;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 批量保存拦截器,拦截 {@link Executor#update(MappedStatement, Object)}方法,
* 发现是批量保存方法,则开启Batch模式并执行单条插入方法,当执行的数量达到预期的值时,
* 执行提交后继续保存剩下的
*/
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class BatchInsertInterceptor implements Interceptor {
/**
* 批量SqlSession构造器,通过它打开一个批量模式的SqlSession
*/
private BatchSqlSessionBuilder batchSqlSessionBuilder;
/**
* 非spring模式下缓存Mapper的类
*/
private final Map> mapperClassMap = new HashMap<>();
/**
* 非spring模式下缓存mapper的类和方法>
*/
private final Map, Map> mapperClassMethodMap = new HashMap<>();
/**
* 拦截方法,一切的入口,由此发生
*
* @param invocation 拦截的调用器
* @return 访问数据库的返回结果
* @throws Throwable 运行时异常
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 先获取参数
Object[] argsObjects = invocation.getArgs();
// 根据配置,拦截的是 org.apache.ibatis.executor.Executor#update方法,依次获取参数
// 第一额参数是MappedStatement
MappedStatement mappedStatement = (MappedStatement) argsObjects[0];
// 根据sql指令类型判断,不是插入的直接执行sql
if (mappedStatement.getSqlCommandType() != SqlCommandType.INSERT) {
return invocation.proceed();
}
// 获取mappedStatementId,如果是约定的.egd-singleInsert结尾的,表明已经是单条保存方法了,直接执行sql
String id = mappedStatement.getId();
if (id.endsWith(BatchInsertContext.EGD_SINGLE_INSERT)) {
return invocation.proceed();
}
// 其他保存方法,先声明一个BatchInsert,后面赋值,并根据这个batchInsert执行sql
BatchInsert batchInsert;
if (BatchInsertContext.isInSpring()) {
// spring环境下,直接调用上下文的方法判断是否是批量保存方法
if (BatchInsertContext.isBatchInsertMappedStatement(id)) {
// 是批量保存方法,获取它的BatchInsert注解
batchInsert = BatchInsertContext.getBatchInsertByMappedStatementId(id);
} else {
// 不是批量保存方法,直接执行sql
return invocation.proceed();
}
} else {
// 非spring环境,需要反射获取类、方法、方法注解来判断
Class> mapperClass = findMapperClass(id);
Method mapperMethod = findMapperMethod(id, mapperClass);
// 获取的方法为空,基本不可能出现,防止其他插件用了类似的机制,所以此处直接执行sql,让其他插件来解决
if (mapperMethod == null) {
return invocation.proceed();
}
// 获取方法的BatchInsert注解
batchInsert = findBatchInsert(id, mapperMethod);
if (batchInsert == null) {
// 这个方法不具有BatchInsert注解,不是批量保存的方法,直接执行sql
return invocation.proceed();
}
}
// 这是方法入参,由开发者编写Mapper接口里的方法生成,正常情况下的调用都会是一个ParamMap类型的参数
Object object = argsObjects[1];
// 非HashMap的,直接往后执行
if (!(object instanceof HashMap)) {
return invocation.proceed();
}
// 3.5.5之前的版本大都是DefaultSqlSession#StrictMap,后面的版本大都是MapperMethod#ParamMap,兼容两者
// ParamMap从3.2.0版本开始引入,instanceof 判断效率更高
if (object instanceof ParamMap || object instanceof DefaultSqlSession.StrictMap) {
// 获取集合参数,
Collection> itemList = getItemList((HashMap, ?>) object, batchInsert);
// 执行批量保存
return invokeBatchInsert(mappedStatement, batchInsert, itemList, (HashMap, ?>) object);
} else {
return invocation.proceed();
}
}
/**
* 执行批量保存,核心方法,在这里开启批量模式,并执行保存方法
*
* @param mappedStatement MappedStatement
* @param batchInsert batchInsert
* @param itemList 集合入参
* @param paramMap 其他参数
* @return Object,一般都是void
*/
private Object invokeBatchInsert(MappedStatement mappedStatement, BatchInsert batchInsert, Collection> itemList, HashMap, ?> paramMap) {
int updateCounts = 0;
// 先获取SqlSession,必须是Batch模式的
SqlSession sqlSession = openSession(batchInsert.flushStatements());
try {
// 批量提交数量
int batchSize = batchInsert.batchSize();
// 索引器
int index = 1;
// 可能的单条保存方法入参
ParamMap