Bootstrap

MybatisPlus 主键为自增id时根据特定多字段判断是否在数据库内存在该条记录,存在则更新、不存在则插入

目录

需求

单主键校验

非主键多字段校验

多主键校验


需求

        主键字段使用了自增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);
            }

        });
    }

;