温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

MyBatis-plus批量插入的通用方法是什么

发布时间:2023-04-10 16:15:03 阅读:124 作者:iii 栏目:开发技术
开发者测试专用服务器限时活动,0元免费领,库存有限,领完即止! 点击查看>>

这篇文章主要介绍“MyBatis-plus批量插入的通用方法是什么”,在日常操作中,相信很多人在MyBatis-plus批量插入的通用方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MyBatis-plus批量插入的通用方法是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

1. MyBatis-plus 的批量保存方法

MyBatis-plus 中默认提供了一个批量保存数据到数据库的方法,也就是 IService#saveBatch() 接口方法。这个方法的实现为 ServiceImpl#saveBatch(),其源码实际处理的关键如下,从中可以知道 IService#saveBatch() 并不是一个真正的批量插入数据的方法

  1. 调用 ServiceImpl#sqlStatement() 使用 SqlMethod.INSERT_ONE 枚举结合实体类确定一个全路径方法名称,这个名称将用于匹配实体对应的库表的单个插入方法的 MappedStatement 对象

  2. 调用 ServiceImpl#executeBatch() 方法遍历 Entity 的集合,使用单个插入的方法为每个实体组装一个 INSERT INTO 语句,遍历结束后 flush,一次性将所有生成的 INSERT INTO 语句推给数据库执行

举例来说,如果调用 IService#saveBatch() 方法保存有2个元素的实体集合 List<Node> 数据到数据库,其执行的 SQL 语句如下

存在 2 条:
INSERT INTO node (name, version) VALUES (&lsquo;nathan&rsquo;,1);
INSERT INTO node (name, version) VALUES (&lsquo;bob&rsquo;,1);

而如果是数据库批量插入,其执行的 SQL 语句应该如下

只有 1 条:
INSERT INTO node (name, version) VALUES (&lsquo;nathan&rsquo;,1), (&lsquo;bob&rsquo;,1);

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }
    
    protected String sqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.table(entityClass).getSqlStatement(sqlMethod.getMethod());
    }
    
    protected <E> boolean executeBatch(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(sqlSession -> {
            int size = list.size();
            int i = 1;
            for (E element : list) {
                consumer.accept(sqlSession, element);
                if ((i % batchSize == 0) || i == size) {
                    sqlSession.flushStatements();
                }
                i++;
            }
        });
    }

2. MyBatis-plus 的批量插入方法

2.1 通用批量插入方法 InsertBatchSomeColumn

事实上 MyBatis-plus 提供了真正的批量插入方法 InsertBatchSomeColumn,只不过这个方法只在 MySQL 数据库下测试过,所以没有将其作为默认通用方法添加到 SqlMethod 中

从其源码实现不难看出,InsertBatchSomeColumn 其实就是提供了一个使用 foreach 标签的 SQL 脚本,不了解这个标签的读者参考自定义批量插入大致理解即可

/**
 * 批量新增数据,自选字段 insert
 * <p> 不同的数据库支持度不一样!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!!  只在 mysql 下测试过!!! </p>
 * <p> 除了主键是 <strong> 数据库自增的未测试 </strong> 外理论上都可以使用!!! </p>
 * <p> 如果你使用自增有报错或主键值无法回写到entity,就不要跑来问为什么了,因为我也不知道!!! </p>
 * <p>
 * 自己的通用 mapper 如下使用:
 * <pre>
 * int insertBatchSomeColumn(List<T> entityList);
 * </pre>
 * </p>
 *
 * <li> 注意: 这是自选字段 insert !!,如果个别字段在 entity 里为 null 但是数据库中有配置默认值, insert 后数据库字段是为 null 而不是默认值 </li>
 *
 * <p>
 * 常用的 {@link Predicate}:
 * </p>
 *
 * <li> 例1: t -> !t.isLogicDelete() , 表示不要逻辑删除字段 </li>
 * <li> 例2: t -> !t.getProperty().equals("version") , 表示不要字段名为 version 的字段 </li>
 * <li> 例3: t -> t.getFieldFill() != FieldFill.UPDATE) , 表示不要填充策略为 UPDATE 的字段 </li>
 *
 * @author miemie
 * @since 2018-11-29
 */
@NoArgsConstructor
@AllArgsConstructor
public class InsertBatchSomeColumn extends AbstractMethod {

    /**
     * 字段筛选条件
     */
    @Setter
    @Accessors(chain = true)
    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 -> i.getInsertSqlProperty(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 (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
            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);
    }

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

2.2 InsertBatchSomeColumn 的使用

由于InsertBatchSomeColumn 是框架已经定义好的通用方法,所以使用者只要引入即可,简单来说只需要进行以下几个步骤:

  • 新增 SQL 注入器

  • 新增配置类将 SQL 注入器添加到容器

  • 新增基类 Mapper,注意这个基类中的批量插入方法名称要和 InsertBatchSomeColumn#getMethod() 方法返回的字符串一致,也就是 insertBatchSomeColumn

具体做法读者请参考 MyBatis-plus 自定义通用方法及其实现原理,本文不再赘述

 经过以上配置,最终具体的业务类 Mapper 只要继承新增的基类 Mapper 就具备了批量插入的功能,笔者习惯将 Mapper 封装在一个 RepositoryService 中对外提供能力,则各个业务类只需要实现类似如下的 NodeRepositoryServiceImpl#insertBatch() 方法即可以对外提供批量插入的功能

    @Override
    public int insertBatch(List<Node> entityList) {
        if (CollectionUtils.isEmpty(entityList)) {
            return 0;
        }
        return getBaseMapper().insertBatchSomeColumn(entityList);
    }

3. 批量插入 MySQL 数据库的坑

3.1 MySQL 对非 NULL 字段插入 NULL 值的处理

使用 MyBatis-plus 批量插入的方法插入 MySQL 记录时需要注意,调用批量插入的方法一定要保证确实是要插入多条数据,如果调用批量插入的方法只插入了单条数据,非常有可能遇到非 NULL 字段插入 NULL 值的错误:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'xxx_name' cannot be null

这是因为我们借助 Entity 插入数据时经常会忽略一些表中有默认值的非 NULL 字段对应的属性的赋值,而从批量插入的 SQL 语句的执行角度来看,这样做也就是往非 NULL 字段插入了 NULL 值。实际上 MySQL 对于非 NULL 字段插入 NULL 值是有兼容处理的,感兴趣的读者可前往 官方传送门,本文摘录如下:

简单来说,对于插入 NULL 值到非 NULL 字段的情况分为两种处理方式:

  • 如果是批量插入多条数据,则会将 NULL 值转化为默认值插到非 NULL 字段(也就是本文批量插入方法插入多条数据的情形)

  • 如果是单条数据插入,则抛出异常,失败结束(对应本文批量插入方法只插入了单条数据的情形)

Inserting NULL into a column that has been declared NOT NULL. For multiple-row INSERT statements or 
INSERT INTO ... SELECT statements, the column is set to the implicit default value for the column
data type. This is 0 for numeric types, the empty string (''for string types, and the “zero” value
for date and time types. INSERT INTO ... SELECT statements are handled the same way as multiple-row
inserts because the server does not examine the result set from the SELECT to see whether it returns
a single row. (For a single-row INSERTno warning occurs when NULL is inserted into a NOT NULL column.
Instead, the statement fails with an error.)

3.2 解决方法

解决方法很简单,只要在批量插入的时候判断一下 Entity 集合的大小即可,如果集合中只有一条数据,则调用插入单条数据的方法

  • MyBatis-plus 单条数据插入之所以不会有往非 NULL 字段插入 NULL 值的问题,是因为其单条插入数据的 SQL 脚本能根据 Entity 的属性赋值情况动态调整,对于 Entity 中值为 NULL 的属性,默认不会将其对应的字段添加到执行的 SQL 语句中

举例来说,如 Node 含有两个属性,分别是 name 和 version,则对于属性值不同的情况最终执行的 SQL 语句也不一样

1. version 为 NULL
INSERT INTO node (name) VALUES (&lsquo;nathan&rsquo;);
2. version 不为 NULL
INSERT INTO node (name, version) VALUES (&lsquo;nathan&rsquo;,1);

    @Override
    public int insertBatch(List<Node> entityList) {
        if (CollectionUtils.isEmpty(entityList)) {
            return 0;
        }
        if (1 == entityList.size()) {
            return getBaseMapper().insert(entityList.get(0));
        }
        return getBaseMapper().insertBatchSomeColumn(entityList);
    }

到此,关于“MyBatis-plus批量插入的通用方法是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

原文链接:https://blog.csdn.net/weixin_45505313/article/details/121574166

AI

开发者交流群×