最近基于自己公司内部服务维护,发现其中调度中心近期出现不少错误日志,但是该任务却是正常执行,生成的报表数据也是正常的,所以很多天没有发现问题
这就匪夷所思了,
经仔细排查发现,是触发了feign超时hystrix熔断器机制
也就是说子服务出现了执行时间过长的情况
是什么让它花费这么多时间去执行呢,只有一个for循环,组装list<object>
这个组装过程在java看来是非常快,根本不可能出现问题
我发现了
iXxxxService.saveBatch(xxxx);
mybatisplus3.3.2自带的批量保存的sql接口
跟踪代码的实现
在接口发现IService
@Transactional(
rollbackFor = {Exception.class}
)
default boolean saveBatch(Collection<T> entityList) {
return this.saveBatch(entityList, 1000);
}
boolean saveBatch(Collection<T> entityList, int batchSize);
ServiceImpl的实现
@Transactional(
rollbackFor = {Exception.class}
)
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = this.sqlStatement(SqlMethod.INSERT_ONE);
return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
sqlSession.insert(sqlStatement, entity);
});
}
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
return !CollectionUtils.isEmpty(list) && this.executeBatch((sqlSession) -> {
int size = list.size();
int i = 1;
for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
E element = var6.next();
consumer.accept(sqlSession, element);
if (i % batchSize == 0 || i == size) {
sqlSession.flushStatements();
}
}
});
}
可以看到这个是累计到一定数量一起 flush。
很多人认为这是mybatisplus设计的一个缺陷,是一条一条去做插入,其实这是错误,这种写法不仅没错还写的非常负责,具体接下来看
方法一
首先要结合数据库驱动来配合,大家注意这个 rewriteBatchedStatements 玩意,其实mybatisplus批量保存与这个的首肯有很大关系
没有加它之前
这是没加之前最好的成绩
加了之后最差的成绩
可以非常直观的看出效率明显提高了好几倍,所以呢千万别误会mybatisplus这个设计,人家完全交给你自主控制,你非得说是它的问题这就不好了
方法二
相比上面方法一的就比较粗暴了
我直接拿过来重写saveBatch,或者增加一个特殊的批量保存
第一步
继承mybatisplus自带的BaseMapper(这里为什么要继承我就不说了哈,懂的都懂),添加我们自定义的批量保存方法
zxsSaveBatch
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.Collection;
public interface BaseMapperPlus <T> extends BaseMapper<T> {
Integer zxsSaveBatch(Collection<T> entityList);
}
第二步
继承AbstractMethod,因为我们要改写它的批量插入语句,换成我们自己想要实现的方式
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import java.util.List;
import java.util.function.Predicate;
@NoArgsConstructor
@AllArgsConstructor
public class ZxsSaveBatch extends AbstractMethod {
/**
* 字段筛选条件
*/
@Setter
@Accessors(chain = true)
private Predicate<TableFieldInfo> predicate;
@SuppressWarnings("Duplicates")
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = new NoKeyGenerator();
SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
String keyProperty = null;
String keyColumn = null;
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
if (tableInfo.havePK()) {
if (tableInfo.getIdType() == IdType.AUTO) {
/* 自增主键 */
keyGenerator = new Jdbc3KeyGenerator();
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
} else {
if (null != tableInfo.getKeySequence()) {
keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
}
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
}
@Override
public String getMethod(SqlMethod sqlMethod) {
// 自定义 mapper 方法名
return "zxsSaveBatch";
}
}
第三步
继承DefaultSqlInjector,把我们的方法添加进去
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import java.util.List;
public class ZxsSqlIntorPlus extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);//获取之前所有的方法
methodList.add(new ZxsSaveBatch()); //添加自己的方法
return methodList;
}
}
第四步
注入到spring
@Bean
public ZxsSqlIntorPlus zxsSqlIntorPlus() {
return new ZxsSqlIntorPlus();
}
第五步
使用方法
之前我们是通过service.saveBatch(Xxxx)来实现批量插入的
这里我们需要改个地方,将原本的BaseMapper改成我们新创建的BaseMapperPlus
这是我的例子,当然,你也可以是其它的
public interface TestMapper extends BaseMapper<Test> {
}
改为
public interface TestMapper extends BaseMapperPlus<Test> {
}
然后在service里面通过baseMapper.zxsSaveBatch(Xxxx)
当然你也可以重写mybatisplus中IService的批量保存的
测一下速度,发现不用设置rewriteBatchedStatements,执行速度也更快了,几乎和rewriteBatchedStatements=true的速度相当