MyBatis-plus批量插入的通用方法是什么
这篇文章主要介绍“MyBatis-plus批量插入的通用方法是什么”,在日常操作中,相信很多人在MyBatis-plus批量插入的通用方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”MyBatis-plus批量插入的通用方法是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
1. MyBatis-plus 的批量保存方法
MyBatis-plus 中默认提供了一个批量保存数据到数据库的方法,也就是 IService#saveBatch() 接口方法。这个方法的实现为 ServiceImpl#saveBatch(),其源码实际处理的关键如下,从中可以知道 IService#saveBatch() 并不是一个真正的批量插入数据的方法
调用 ServiceImpl#sqlStatement() 使用 SqlMethod.INSERT_ONE 枚举结合实体类确定一个全路径方法名称,这个名称将用于匹配实体对应的库表的单个插入方法的 MappedStatement 对象
调用 ServiceImpl#executeBatch() 方法遍历 Entity 的集合,使用单个插入的方法为每个实体组装一个 INSERT INTO 语句,遍历结束后 flush,一次性将所有生成的 INSERT INTO 语句推给数据库执行
举例来说,如果调用 IService#saveBatch() 方法保存有2个元素的实体集合 List<Node> 数据到数据库,其执行的 SQL 语句如下
存在 2 条:
INSERT INTO node (name, version) VALUES (‘nathan’,1);
INSERT INTO node (name, version) VALUES (‘bob’,1);而如果是数据库批量插入,其执行的 SQL 语句应该如下
只有 1 条:
INSERT INTO node (name, version) VALUES (‘nathan’,1), (‘bob’,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 脚本,不了解这个标签的读者参考自定义批量插入大致理解即可
@NoArgsConstructor@AllArgsConstructorpublic 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 columndata type. This is 0 for numeric types, the empty string ('') for string types, and the “zero” valuefor date and time types. INSERT INTO ... SELECT statements are handled the same way as multiple-rowinserts because the server does not examine the result set from the SELECT to see whether it returnsa single row. (For a single-row INSERT, no 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 语句也不一样
version 为 NULL
INSERT INTO node (name) VALUES (‘nathan’);
2. version 不为 NULL
INSERT INTO node (name, version) VALUES (‘nathan’,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批量插入的通用方法是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341