Mybatis真实批量插入-重写方法 InsertBatchSomeColumn

本文介绍了如何在MyBatisPlus中添加自定义SQL注入器,解决批量插入时null属性的默认值问题,并提供了一种处理大数据插入限制的策略,包括通用mapper的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、添加自定义方法SQL注入器

//将批量插入的方法添加method
public class CustomSqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        // 获取父类SQL注入方法列表
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        // 将批量插入方法添加进去,这个方法在下方会重写
        methodList.add(new CustomSqlInjector ());
        return methodList;
    }
}

二、方法添加到spring管理

//将上方的方法托管到spring管理
@Configuration
public class MybatisPlusConfig {

    @Bean
    public CustomSqlInjector customSqlInjector() {
        return new CustomSqlInjector();
    }

}

三、解决中批量插入null 属性不能使用默认值的问题

就是重新写一下InsertBatchSomeColumn 这个类,然后修改其中的加载参数方法

上述流程只能保证可以使用,但是还是存在限制必须全字段有值,或者可以为null

/**
 * 增强版的批量插入,解决mp 中批量插入null 属性不能使用默认值的问题
 */

@SuppressWarnings("serial")
@AllArgsConstructor
@NoArgsConstructor
public class InsertBatchSomeColumn extends AbstractMethod {

    /**
     * 字段筛选条件
     */
    private Predicate<TableFieldInfo> predicate;

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
                this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
        //重写方法,主要修改的是设置赋默认值的方法
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
                this.filterTableFieldInfo(fieldList, predicate, i -> getInsertSqlProperty(i, ENTITY_DOT), EMPTY);
        insertSqlProperty =
                LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (tableInfo.havePK()) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主键 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator,
                keyProperty, keyColumn);
    }

    public String getMethod(SqlMethod sqlMethod) {
        // 自定义 mapper 方法名
        return "insertBatchSomeColumn";
    }

    /**
     * <p>
     * 重写该方法,项目启动加载的时候会设置
     * 如果对应的参数值为空,就使用数据库的默认值
     * </p>
     *
     * @param tableFieldInfo
     * @param prefix
     * @return
     */
    private String getInsertSqlProperty(TableFieldInfo tableFieldInfo, final String prefix) {
        String newPrefix = prefix == null ? "" : prefix;
        String elPart = SqlScriptUtils.safeParam(newPrefix + tableFieldInfo.getEl());
        //属性为空时使用默认值
        String result =
                SqlScriptUtils.convertIf(elPart, String.format("%s != null", newPrefix + tableFieldInfo.getEl()),
                        false) + SqlScriptUtils.convertIf("default",
                        String.format("%s == null", newPrefix + tableFieldInfo.getEl()), false);
        return result + ",";
    }

}

四、添加通用mapper&解决大数据插入InsertBatchSomeColumn报错问题

报错原因:数据量过大超过mysql的单次传输限制
查询限制:select @@max_allowed_packet/(1024*1024);

如果数据量过大建议调用分页进行批量插入,因为sql如果拼接过大,在sql解析的时候也会比较耗时

/**
 * 自定义Mapper,添加批量插入接口
 */
public interface CustomMapper<T> extends BaseMapper<T> {

    /**
     * <p>
     *     批量插入,方法的名字是固定的不能随意设置
     *     命名来源InsertBatchSomeColumn.getMethod()
     * </p>
     * @param entityList 实体列表
     * @return 影响行数
     */
    Integer insertBatchSomeColumn(Collection<T> entityList);
    
    /**
     * <p>
     *     解决大数据插入InsertBatchSomeColumn报错问题。
     * </p>
     * @param entityList
     * @param batchSize
     */
    @Transactional(rollbackFor = {Exception.class})
    default void saveBatchsss(Collection<T> entityList, int batchSize) {
        int size = entityList.size();
        int idxLimit = Math.min(batchSize, size);
        int i = 1;
        //保存单批提交的数据集合
        List<T> oneBatchList = new ArrayList<>();
        for (Iterator<T> var7 = entityList.iterator(); var7.hasNext(); ++i) {
            T element = var7.next();
            oneBatchList.add(element);
            if (i == idxLimit) {
                insertBatchSomeColumn(oneBatchList);
                //每次提交后需要清空集合数据
                oneBatchList.clear();
                idxLimit = Math.min(idxLimit + batchSize, size);
            }
        }
    }

}

想了解更多的请关注博主另一篇文章MyBatis 批量插入-优化&效率对比

### 使用 MyBatis-Plus 实现批量插入并过滤已存在的数据 在实际项目中,当需要通过 MyBatis-Plus 实现批量插入操作时,如果希望过滤掉已经存在于数据库中的记录,则可以通过自定义 SQL 和逻辑来实现这一需求。以下是具体的解决方案: #### 自定义批量插入逻辑 由于 `saveBatch()` 方法本质上会执行多条独立的 `INSERT` 语句[^1],因此无法满足高效处理的需求。对于 SQL Server 数据库而言,可以利用其特有的语法特性(如 `MERGE INTO` 或者带有条件判断的 `INSERT`) 来完成此功能。 一种推荐的方式是重写默认行为或者调用原生 SQL 插入接口。例如,MyBatis-Plus 提供了一个更灵活的方法——`insertBatchSomeColumn()`,它允许开发者指定哪些列参与插入过程,并且支持字段级别的控制[^3]。 为了进一步优化性能以及解决重复项问题,可以在业务层先查询目标表中存在的键值集合再决定提交新增的数据子集;另一种方式是在存储过程中加入冲突检测机制从而避免客户端额外往返网络开销。 下面是基于上述思路的一个代码实例演示如何结合注解与手动拼接SQL达成目的: ```java @Override public boolean batchInsertWithFilter(List<YourEntity> list){ List<Object> existingKeys = baseMapper.selectList(new QueryWrapper<YourEntity>().select("unique_key_column")).stream() .map(item -> item.getUniqueKey()) // 假设实体类中有getUniqueKey方法获取唯一标识符 .collect(Collectors.toList()); List<YourEntity> filteredList = list.stream().filter(entity->!existingKeys.contains(entity.getUniqueKey())).toList(); return this.baseMapper.insertBatchSomeColumn(filteredList); } ``` 另外需要注意的是,在某些场景下可能还需要考虑并发环境下的竞争状况,这时应评估是否引入事务管理或是锁策略以保障最终一致性[^2]。 最后提醒一点关于官方文档资源链接可以帮助深入理解框架设计理念及其扩展能力[^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

常有理_

老板们赏点元子吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值