目录

需求

单主键校验

非主键多字段校验

多主键校验


需求

主键字段使用了自增id做了优化,但是需求需要依赖两个联合唯一的字段校验该条记录是否存在,如果存在则执行更新操作,不存在则执行插入操作。

单主键校验

一开始打算手写sql,使用SelcetKey + foreach 先根据两个字段查出对应的id,如果id不存在则插入,否则更新,但是底层是单条多次的sql提交,很影响性能。

后来发现的ServiceImpl类下有一个saveOrUpdateBatch方法:

@Transactional(rollbackFor = {Exception.class})public boolean saveOrUpdateBatch(Collection 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 param = new ParamMap();param.put("et", entity);sqlSession.update(this.getSqlStatement(SqlMethod.UPDATE_BY_ID), param);});}

看起来已经帮我们封装好了,但是它针对的是单主键的校验,我们的主键是数据库自增的,需要校验的是非主键字段,不太适用。

非主键多字段校验(主要)

最终优化了一下代码,直接在Service实现里调用即可:

public boolean saveOrUpdateBatch2(List list, int batchSize) { return SqlHelper.executeBatch(entityClass, log, list, batchSize, (sqlSession, entity) -> {HashMap param = new HashMap();LambdaQueryWrapper eq = Wrappers.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  boolean executeBatch(Class entityClass, Log log, Collection list, int batchSize, BiConsumer consumer) {Assert.isFalse(batchSize  {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 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 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 param = new ParamMap();param.put("et", entity);sqlSession.update(tableInfo.getSqlStatement("updateByMultiId"), param);} else {sqlSession.insert(tableInfo.getSqlStatement(SqlMethod.INSERT_ONE.getMethod()), entity);}});}