baomidou / mybatis-plus

An powerful enhanced toolkit of MyBatis for simplify development
https://baomidou.com
Apache License 2.0
16.25k stars 4.29k forks source link

ServiceImpl新增方法,基于wrapper批量更新,根据ID 批量修改插入优化查询效率,希望能优化进去 #6249

Closed sgps000 closed 2 months ago

sgps000 commented 2 months ago
/**
     * 根据wrapper 批量更新
     *
     * @param ewList wrapper对象集合
     * @return boolean
     * @since 3.5.6 日期:2024-06-11
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMulti(Collection<? extends Wrapper<T>> ewList) {
        return updateMulti(ewList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 根据wrapper 批量更新
     *
     * @param ewList    wrapper对象集合
     * @param batchSize 更新批次数量
     * @return boolean
     * @since 3.5.6 日期:2024-06-11
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMulti(Collection<? extends Wrapper<T>> ewList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE);
        return executeBatch(ewList, batchSize, (sqlSession, ew) -> {
            MapperMethod.ParamMap<Wrapper<T>> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, null);
            param.put(Constants.WRAPPER, ew);
            sqlSession.update(sqlStatement, param);
        });
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     * @return boolean
     * @since 3.5.6 日期:2024-06-11
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean upsertMulti(Collection<T> entityList) {
        return upsertMulti(entityList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 根据ID 批量修改插入
     *
     * @param entityList 实体对象集合
     * @param batchSize  更新批次数量
     * @return boolean
     * @since 3.5.6 日期:2024-06-11
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean upsertMulti(Collection<T> entityList, int batchSize) {
        if (CollectionUtils.isEmpty(entityList)) return false;
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        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!");

        String selectSqlStatement = getSqlStatement(SqlMethod.SELECT_OBJS);
        String insertSqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        String updateSqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);

        Consumer<SqlSession> consumer = sqlSession -> {
            int size = entityList.size();
            int idxLimit = Math.min(batchSize, size);
            int i = 1;
            // 批处理临时集合
            List<T> batchList = new ArrayList<>();

            for (T element : entityList) {
                batchList.add(element);

                // 批处理
                if (i == idxLimit) {
                    // 批数据id集合
                    List<Object> idList = batchList.stream()
                            .map(e -> tableInfo.getPropertyValue(e, keyProperty))
                            .filter(StringUtils::checkValNotNull)
                            .toList();

                    // 查询批数据中已存在的id集合
                    List<Object> existedIdList = null;
                    if (!idList.isEmpty()) {
                        Map<String, Object> params = new HashMap<>();
                        params.put(Constants.WRAPPER, Wrappers.query().select(keyProperty).in(keyProperty, idList));
                        existedIdList = sqlSession.selectList(selectSqlStatement, params);
                    }

                    // 遍历批数据
                    for (T entity : batchList) {
                        Object id = tableInfo.getPropertyValue(entity, keyProperty);
                        if (StringUtils.checkValNull(id) || CollectionUtils.isEmpty(existedIdList) || !existedIdList.contains(id)) {
                            sqlSession.insert(insertSqlStatement, entity);
                        } else {
                            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
                            param.put(Constants.ENTITY, entity);
                            sqlSession.update(updateSqlStatement, param);
                        }
                    }

                    // 提交sql
                    sqlSession.flushStatements();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                    // 清空已处理的数据
                    batchList.clear();
                }
                i++;
            }
        };
        return SqlHelper.executeBatch(getSqlSessionFactory(), log, consumer);
    }
image

旧版每个对象都会查询校验一遍,效率极低。还有就是校验数据是否存在,为啥用selectById,而不是用count查询校验?

sgps000 commented 2 months ago

由于之前使用mongodb设计的原因,实体类中特殊类型都会重写get方法,导致有些字段永远不会为空,直接使用entity类型的更新容易出错,所以更多的是想要通过wrapper去主动精准地操作数据库。原本还想实现一个基于wrapper的批量修改插入的,但是想到批量校验需要在java中,通过wrapper直接校验对象是否符合条件,暂时没找到合适的办法,只能用MERGE INTO语句自定义注入器去实现基础功能,没法像mongodb的upsert那样方便。

nieqiurong commented 2 months ago

6229

nieqiurong commented 2 months ago

自己业务代码自己处理,不要什么都希望框架帮你做.

sgps000 commented 2 months ago

自己业务代码自己处理,不要什么都希望框架帮你做.

@nieqiurong 第一个方法是基于wrapper批量更新,如果算个人业务我能理解。 但第二个批量更新插入,我感觉是框架代码的写法有问题,建议改掉。 原框架中serviceImpl的saveOrUpdateBatch那么慢,3.5.7的BaseMapper中InsertOrUpdate也没有改变写法,是用的人少才没人反馈吗?还是我的用法错了?

image
sgps000 commented 2 months ago

关于第一个方法,我是在想既然update(Wrapper ew)方法能有,updateBatchById(Collection entityList, int batchSize)也有,为啥不加一个updateBatch(Collection<Wrapper> ewList, int batchSize),我是觉得这个方法跟官方的比较搭才会捎带上,其他特别定制的自定义方法我就没放了。但我不是参与者,也还不熟练使用git,所以直接写好可行的代码建议官方能加进去。或者能否为我解惑一下,为啥不建议批量使用Wrapper更新吗?

agibso38 commented 2 days ago

确实saveOrUpdateBatch好慢,为什么不先批量查出来比对后在批量新增或修改