一、saveOrUpdateBatch源码简单介绍
下面代码是Mybatis plus的源代码,具体位置为ServiceImpl的saveOrUpdateBatch方法。在之前文章中有对改方法进行过详细的解析。
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
return StringUtils.checkValNull(idVal)
|| CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
}, (sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
});
}
从源码中提取出的两个lambda代码,这两段代码也就是我们主要要更改的代码。
//predicate查询逻辑
(sqlSession, entity) -> {
Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
return StringUtils.checkValNull(idVal)
|| CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
}
//consumer更新逻辑
(sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
}
二、改造目标
该方法特点是在查询和更新时必须通过主键,调用的SqlMethod为UPDATE_BY_ID。并且使用该方法时实体表中的主键必须有且仅有一个字段。所以导致多个字段的组合键不支持,不通过主键更新也不支持。所以最需要解决的问题就是将该方法更改为普通查询和更新,以方便支持多主键或其他字段的更新操作。注:mybatis plus本身不支持多主键更新,将主键查询更改为普通查询并不是本质上支持多主键。
三、内部逻辑
SqlHelper.saveOrUpdateBatch方法
通过下面源代码逻辑以及注释,可以看出mybitis plus的saveOrUpdate的更新逻辑:predicate查询为空调用insert,否则调用consumer更新逻辑
/**
* 批量更新或保存
*
* @param entityClass 实体
* @param log 日志对象
* @param list 数据集合
* @param batchSize 批次大小
* @param predicate predicate(新增条件) notNull
* @param consumer consumer(更新处理) notNull
* @param <E> E
* @return 操作结果
* @since 3.4.0
*/
public static <E> boolean saveOrUpdateBatch(Class<?> entityClass, Class<?> mapper, Log log, Collection<E> list, int batchSize, BiPredicate<SqlSession, E> predicate, BiConsumer<SqlSession, E> consumer) {
String sqlStatement = getSqlStatement(mapper, SqlMethod.INSERT_ONE);
return executeBatch(entityClass, log, list, batchSize, (sqlSession, entity) -> {
if (predicate.test(sqlSession, entity)) {
sqlSession.insert(sqlStatement, entity);
} else {
consumer.accept(sqlSession, entity);
}
});
}
executeBatch
执行逻辑:循环调用,每batchSize个数据执行一次sqlSession.flushStatements提交一次sql。
/**
* 执行批量操作
*
* @param entityClass 实体类
* @param log 日志对象
* @param list 数据集合
* @param batchSize 批次大小
* @param consumer consumer
* @param <E> T
* @return 操作结果
* @since 3.4.0
*/
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
int size = list.size();
int idxLimit = Math.min(batchSize, size);
int i = 1;
for (E element : list) {
consumer.accept(sqlSession, element);
if (i == idxLimit) {
sqlSession.flushStatements();
idxLimit = Math.min(idxLimit + batchSize, size);
}
i++;
}
});
}
四、基于源码,对查询和更新逻辑改造,实现批量更新
在查询逻辑中将原本的主键判断逻辑去除,调用selectById方法改成selectList方法。并且使用的Wrapper条件通过参数传递进来,实现通过自定义的Sql逻辑来判断数据库是否存在数据。
相同的在更新逻辑中,增加了更新的Wrapper条件(和查询一致),使得能够在更新时使用wrapper条件。
public abstract class BaseServiceImpl<R extends BaseMapper<T>, T> extends ServiceImpl<R, T> implements IBaseService<T> {
/**
* 默认批次提交数量
*/
private final int DEFAULT_BATCH_SIZE = 1000;
/**
* 单次提交保存或更新
*/
@Override
public boolean mySaveOrUpdate(T entity, Function<LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> queryWrapper) {
LambdaQueryWrapper<T> wrapper = queryWrapper.apply(wrapper());
return CollectionUtils.isEmpty(list(wrapper)) ? save(entity) : update(entity, wrapper);
}
/**
* 批量提交保存或更新
*/
@Override
public boolean mySaveOrUpdateBatch(Collection<T> entityList,
BiFunction<T, LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> queryWrapper) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
Assert.notNull(tableInfo, "错误:没有找到实体类");
//判断是否存在的方法
BiPredicate<SqlSession, T> predicate = (sqlSession, entity) -> {
ParamMap<Object> param = new ParamMap<>();
param.put(Constants.WRAPPER, queryWrapper.apply(entity, wrapper()));
return CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_LIST), param));
};
//更新的方法
BiConsumer<SqlSession, T> consumer = (sqlSession, entity) -> {
ParamMap<Object> param = new ParamMap<>();
param.put(Constants.ENTITY, entity);
param.put(Constants.WRAPPER, queryWrapper.apply(entity, wrapper()));
sqlSession.update(getSqlStatement(SqlMethod.UPDATE), param);
};
return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, DEFAULT_BATCH_SIZE,
predicate, consumer);
}
private LambdaQueryWrapper<T> wrapper() {
return new LambdaQueryWrapper<>();
}
}
接口层,省去了Javadoc
public interface IBaseService<T> extends IService<T> {
boolean mySaveOrUpdate(T entity, Function<LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> queryWrapper);
boolean mySaveOrUpdateBatch(Collection<T> entityList, BiFunction<T, LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> queryWrapper);
}
五、使用方法:
使用时将我们业务的service接口层继承IBaseService接口,业务实现层继承ServiceImpl改成继承BaseServiceImpl,这样我们可以在业务层调用mySaveOrUpdateBatch方法来实现自己的批量保存更新。或者在其他业务代码中,注入对应的service接口,使用对应注入的bean来调用mySaveOrUpdateBatch方法来实现,如下代码:
xxxxService.mySaveOrUpdateBatch(list, (o, q) -> q.eq(Entity::getId, o.getId())
.eq(Entity::getName, o.getName()));
六、其他
mybatis plus的批量更新、保存、保存或更新,原理是使用同一个sqlSession,避免多次创建sqlSession和提交执行。但是任然是循环执行基础的查询、更新,效率比较低,建议数据量不大(不达到上万的数据)的情况使用,本文章改动相同。