目录
需求
主键字段使用了自增id做了优化,但是需求需要依赖两个联合唯一的字段校验该条记录是否存在,如果存在则执行更新操作,不存在则执行插入操作。
单主键校验
一开始打算手写sql,使用SelcetKey + foreach 先根据两个字段查出对应的id,如果id不存在则插入,否则更新,但是底层是单条多次的sql提交,很影响性能。
后来发现的ServiceImpl类下有一个saveOrUpdateBatch方法:
@Transactional(
rollbackFor = {Exception.class}
)
public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!", new Object[0]);
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!", new Object[0]);
return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
return StringUtils.checkValNull(idVal) || CollectionUtils.isEmpty(sqlSession.selectList(this.getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
}, (sqlSession, entity) -> {
ParamMap<T> param = new ParamMap();
param.put("et", entity);
sqlSession.update(this.getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
});
}
看起来已经帮我们封装好了,但是它针对的是单主键的校验,我们的主键是数据库自增的,需要校验的是非主键字段,不太适用。
非主键多字段校验(主要)
最终优化了一下代码,直接在Service实现里调用即可:
public boolean saveOrUpdateBatch2(List<XXXModel> list, int batchSize) {
return SqlHelper.executeBatch(entityClass, log, list, batchSize, (sqlSession, entity) -> {
HashMap param = new HashMap();
LambdaQueryWrapper<XXXModel> eq = Wrappers.<XXXModel>lambdaQuery()
.eq(XXXModel::getXXX1, entity.getXXX1())
.eq(XXXModel::getXXX2, entity.getXXX2());
param.put("ew", eq);
XXXModelmodel = sqlSession.selectOne(getSqlStatement(SqlMethod.SELECT_ONE), param);
if (model == null) {
sqlSession.insert(getSqlStatement(SqlMethod.INSERT_ONE), entity);
} else {
entity.setId(model.getId());
param.put("et",entity);
sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
}
});
}
先查一次数据,判断是否存在,不存在则插入,存在则根据查到的id主键更新。
批量操作时,底层会把sql先缓存在SqlSession内,调用sqlSession.flushStatements()时才会把数据发到数据库执行:
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", new Object[0]);
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) {
//满足一个batchSize 或 传入大小不足一个batchSize时 批处理提交
sqlSession.flushStatements();
}
}
});
}
需要注意的是,如果不使用SqlSession而是直接使用baseMapper来查会报空指针异常,日志也没有打印堆栈信息,异常在底层被捕获了又抛出。(个人猜测跟SqlSession的底层封装有关,一个会话内的sql必须要依赖同一个SqlSession执行)
多主键校验
联合主键的批量插入or更新其实有MppBaseMapper针对原生的BaseMapper做了实现,如果表结构为联合主键,可以分别继承MppBaseMapper、IMppService直接调用即可。具体代码为MppServiceImpl:saveOrUpdateBatchByMultiId():
@Transactional(
rollbackFor = {Exception.class}
)
public boolean saveOrUpdateBatchByMultiId(Collection<T> entityList, int batchSize) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!", new Object[0]);
//这里面其实是获取实体类中使用了MppMultiId注解的字段映射为Map 这个字段是Mpp用来用于标识多个主键的
Map<String, String> idMap = this.checkIdCol(this.entityClass, tableInfo);
Assert.notEmpty(idMap, "entity {} not contain MppMultiId anno", new Object[]{this.entityClass.getName()});
return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
boolean updateFlag = true;
Iterator var6 = idMap.keySet().iterator();
while(var6.hasNext()) {
String attr = (String)var6.next();
//校验主键是否为空 为空则不更新
if (StringUtils.checkValNull(attr)) {
updateFlag = false;
break;
}
}
//走到此处 说明主键均不为空 接着查数据库判断该记录是否存在
if (updateFlag) {
Object obj = this.selectByMultiId(entity);
if (Objects.isNull(obj)) {
updateFlag = false;
}
}
//更新或插入
if (updateFlag) {
ParamMap<T> param = new ParamMap();
param.put("et", entity);
sqlSession.update(tableInfo.getSqlStatement("updateByMultiId"), param);
} else {
sqlSession.insert(tableInfo.getSqlStatement(SqlMethod.INSERT_ONE.getMethod()), entity);
}
});
}