继承ServiceImpl并写一个自己的saveOrUpdateBatch(下)

本文详细分析了Mybatisplus的saveOrUpdateBatch源码,讨论了其限制在单主键场景下,并介绍了如何通过修改查询和更新逻辑,使用LambdaQueryWrapper来实现对多主键或自定义更新条件的支持。作者提供了改造后的BaseServiceImpl和使用示例。
摘要由CSDN通过智能技术生成

一、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本身不支持多主键更新,将主键查询更改为普通查询并不是本质上支持多主键。
SqlMethod枚举

三、内部逻辑

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和提交执行。但是任然是循环执行基础的查询、更新,效率比较低,建议数据量不大(不达到上万的数据)的情况使用,本文章改动相同。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值