持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情
哈喽,大家好,我是一条。
相信大家都用过 Mybati-Plus ,啊哈哈,属实是好用,写起来代码那叫一个丝滑。
List<ZoneReportPO> zoneReportPOList = zoneReportMapper.selectList(
Wrappers.lambdaQuery(ZoneReportPO.class)
.eq(StringUtils.isNotBlank(reportTime), ZoneReportPO::getReportTime, reportTime));
配上函数式编程,美得很。
但是!它在批量插入这里有个坑:
@Service
public class CommerceUserService extends ServiceImpl<CommerceUserMapper, CommerceUser> {
public boolean batchInsert(List<CommerceUser> list) {
return super.saveBatch(list);
}
}
这里有一个帮我们实现好的批量插入方法,看起来挺方便哈,可仔细一看,这货还是一条一条插入的。
return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (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();
}
}
});
感兴趣的同学可以看一下源码,同时有个 java8 的新函数式接口 BiConsumer,可以学习。
我们不跑题说正事,批量插入怎么搞?我插入1000条数据,访问1000次数据库显然是不行,还要考虑大事务。
没办法,自己写吧!开搞!
sql注入器
这里我们可以通过继承DefaultSqlInjector来注入我们自定义的sql方法。
这里实现了批量插入和修改方法。
public class CustomizedSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
//保留自带方法
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new InsertBatchMethod());
methodList.add(new UpdateBatchMethod());
return methodList;
}
}
InsertBatchMethod
接下来就要把这个方法实现,我觉得哈,下面代码直接复制用就行,都是sql的拼接,没什么好研究的。
注意下返回的"insertBatch"这个id,和接口方法中的方法名要对应上。
@Slf4j
public class InsertBatchMethod extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = "<script>insert into %s %s values %s</script>";
String fieldSql = prepareFieldSql(tableInfo);
String valueSql = prepareValuesSql(tableInfo);
String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, "insertBatch", sqlSource, new NoKeyGenerator(), null, null);
}
private String prepareFieldSql(TableInfo tableInfo) {
StringBuilder fieldSql = new StringBuilder();
fieldSql.append(tableInfo.getKeyColumn()).append(",");
tableInfo.getFieldList().forEach(x -> fieldSql.append(x.getColumn()).append(","));
fieldSql.delete(fieldSql.length() - 1, fieldSql.length());
fieldSql.insert(0, "(");
fieldSql.append(")");
return fieldSql.toString();
}
private String prepareValuesSql(TableInfo tableInfo) {
final StringBuilder valueSql = new StringBuilder();
valueSql.append("<foreach collection="list" item="item" index="index" open="(" separator="),(" close=")">");
valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},");
tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},"));
valueSql.delete(valueSql.length() - 1, valueSql.length());
valueSql.append("</foreach>");
return valueSql.toString();
}
}
自定义Mapper
在自定义一个mapper,这样使用的时候就不再继承BaseMapper而是我们的RootMapper。
public interface RootMapper<T> extends BaseMapper<T> {
/**
* 自定义批量插入
*/
int insertBatch(@Param("list") List<T> list);
/**
* 自定义批量更新,条件为主键
*/
int updateBatch(@Param("list") List<T> list);
}
内存溢出怎么办?
你以为这就完事大吉了?并不是,因为我们要把所有数据拼接一个sql,那字符串就会非常的长,内存溢出必不可少了,怎么解决呢?
对于特别大量的数据,我们还是要分多次的批量插入。
引入一个工具类
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
分组批量插入
List<List<TestBean>> splistList = Lists.partition(testBeans,200);
splistList.forEach(itemList->testMapper.insertBatch(itemList));
最后
其实这个问题困扰了我很久,如果遇到数据量大,又要实时插入,效率、性能、内存溢出、大事务。都是需要考虑的。
大家还是要要看需求选择合适的方案。