文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

spring boot集成mybatis-plus——Mybatis Plus 批量 Insert_新增数据(图文讲解)

2023-09-03 21:36

关注

Mybatis Plus 批量 Insert_新增数据(图文讲解)

 更新时间 2023-01-10 16:02:58

前言

大家好,我是小哈。

本小节中,我们将学习如何通过 Mybatis Plus 实现 MySQL 批量插入数据。

什么是批量插入?优势在哪里?

先抛出一个问题:假设老板给你下了个任务,向数据库中添加 100 万条数据,并且不能耗时太久!

通常来说,我们向 MySQL 中新增一条记录,SQL 语句类似如下:

INSERT INTO `t_user` (`name`, `age`, `gender`) VALUES ('犬小哈0', 0, 1); 

如果你需要添加 100 万条数据,就需要多次执行此语句,这就意味着频繁地与数据库建立链接,必然导致网络 IO 开销巨大,并且每一次数据库执行 SQL 都需要进行解析、优化等操作。

幸运的是,MySQL 支持一条 SQL 语句可以批量插入多条记录,格式如下:

INSERT INTO `t_user` (`name`, `age`, `gender`) VALUES ('犬小哈0', 0, 1), ('犬小哈1', 0, 1), ('犬小哈3', 0, 1); 

和常规的 INSERT 语句不同的是,VALUES 支持多条记录,通过 , 逗号隔开。这样,可以实现一次性插入多条记录。

数据量不多的情况下,常规 INSERT 和批量插入性能差距不大,但是,一旦数量级上去后,执行耗时差距就拉开了,在后面我们会实测一下它们之间的耗时对比。

表与实体类

先创建一个测试表 t_user, 执行脚本如下:

DROP TABLE IF EXISTS user; CREATE TABLE `t_user` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名', `age` int(11) NULL DEFAULT NULL COMMENT '年龄', `gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男', PRIMARY KEY (`id`) ) COMMENT = '用户表'; 

再定义一个名为 User 实体类:

 @Data @TableName("t_user") public class User {  @TableId(type = IdType.AUTO) private Long id;  private String name;  private Integer age;  private Integer gender; } 

TIP: @Data 是 Lombok 注解,偷懒用的,加上它即可免写繁杂的 getXXX/setXXX 相关方法,不了解的小伙伴可自行搜索一下如何使用。

Mybatis Plus 伪批量插入

在前面《新增数据》 小节中,我们已经知道了 Mybatis Plus 内部封装的批量插入 savaBatch() 是个假的批量插入,示例代码如下:

List users =new ArrayList<>();for (int i = 0; i < 5; i++) { User user = new User(); user.setName("犬小哈" + i); user.setAge(i); user.setGender(1); users.add(user); } // 批量插入 boolean isSuccess = userService.saveBatch(users); System.out.println("isSuccess:" + isSuccess); 

通过打印实际执行 SQL , 我们发现还是一条一条的执行 INSERT:

并且还带着大家看了内部实现的源码,这种方式比起自己 for 循环一条一条 INSERT 插入数据性能要更高,原因是在会话这块做了优化,虽然实际执行并不是真的批量插入。

利用 SQL 注入器实现真的批量插入

接下来,小哈就手把手带你通过 Mybatis Plus 框架的 SQL 注入器实现一个真的批量插入。

示例项目结构

先贴一张示例项目的结构:

注意看我红线标注的部分,主要关注这 4 个类与接口。

新建批量插入 SQL 注入器

在工程 config 目录下创建一个 SQL 注入器 InsertBatchSqlInjector :

 public class InsertBatchSqlInjector extends DefaultSqlInjector { @Override public List getMethodList(Class mapperClass, TableInfo tableInfo) { // super.getMethodList() 保留 Mybatis Plus 自带的方法 List methodList = super.getMethodList(mapperClass, tableInfo); // 添加自定义方法:批量插入,方法名为 insertBatchSomeColumn methodList.add(new InsertBatchSomeColumn()); return methodList; } } 

说说 InsertBatchSomeColumn

InsertBatchSomeColumn 是 Mybatis Plus 内部提供的默认批量插入,只不过这个方法作者只在 MySQL 数据测试过,所以没有将它作为通用方法供外部调用,注意看注释:

源码复制出来,如下:

@SuppressWarnings("serial") public class InsertBatchSomeColumn extends AbstractMethod {  @Setter @Accessors(chain = true) private Predicate predicate;  public InsertBatchSomeColumn() { // 方法名 super("insertBatchSomeColumn"); }  public InsertBatchSomeColumn(Predicate predicate) { super("insertBatchSomeColumn"); this.predicate = predicate; }  public InsertBatchSomeColumn(String name, Predicate predicate) { super(name); this.predicate = predicate; } @SuppressWarnings("Duplicates") @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE; SqlMethod sqlMethod = SqlMethod.INSERT_ONE; List fieldList = tableInfo.getFieldList(); String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, false) + this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY); String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET; String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, 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 (tableInfo.havePK()) { if (tableInfo.getIdType() == IdType.AUTO) {  keyGenerator = Jdbc3KeyGenerator.INSTANCE; keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, 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); } } 

配置 SQL 注入器

在 config 包下创建 MybatisPlusConfig 配置类:

 @Configuration @MapperScan("com.quanxiaoha.mybatisplusdemo.mapper") public class MybatisPlusConfig {  @Bean public InsertBatchSqlInjector insertBatchSqlInjector() { return new InsertBatchSqlInjector(); } } 

新建 MyBaseMapper

在 config 包下创建 MyBaseMapper 接口,让其继承自 Mybatis Plus 提供的 BaseMapper, 并定义批量插入方法:

 public interface MyBaseMapper<T> extends BaseMapper<T> { // 批量插入 int insertBatchSomeColumn(@Param("list") List batchList); } 

注意:方法名必须为 insertBatchSomeColumn, 和 InsertBatchSomeColumn 内部定义好的方法名保持一致。

新建 UserMapper

在 mapper 包下创建 UserMapper 接口,注意继承刚刚自定义的 MyBaseMapper, 而不是 BaseMapper :

 public interface UserMapper extends MyBaseMapper<User> { } 

测试批量插入

完成上面这些工作后,就可以使用 Mybatis Plus 提供的批量插入功能了。我们新建一个单元测试,并注入 UserMapper :

@Autowiredprivate UserMapper userMapper;

单元测试如下:

@Testvoid testInsertBatch() {    List users = new ArrayList<>(); for (int i = 0; i < 3; i++) { User user = new User(); user.setName("犬小哈" + i); user.setAge(i); user.setGender(1); users.add(user); } userMapper.insertBatchSomeColumn(users); } 

控制台实际执行 SQL 如下:

可以看到这次是真实的批量插入了,舒服了~

性能对比

我们来测试一下插入 105000 条数据,分别使用 for 循环插入数据、savaBatch() 伪批量插入、与真实批量插入三种模式,看看耗时差距多少。

小哈这里的机器配置如下:

for 循环插入

单元测试代码如下:

@Testvoid testInsert1() {    // 总耗时:722963 ms, 约 12 分钟 long start = System.currentTimeMillis(); for (int i = 0; i < 105000; i++) { User user = new User(); user.setName("犬小哈" + i); user.setAge(i); user.setGender(1); userMapper.insert(user); } System.out.println(String.format("总耗时:%s ms", System.currentTimeMillis() - start)); } 

savaBatch() 伪批量插入

单元测试代码如下:

@Testvoid testInsert2() {    // 总耗时:95864 ms, 约一分钟30秒左右 long start = System.currentTimeMillis(); List users = new ArrayList<>(); for (int i = 0; i < 105000; i++) { User user = new User(); user.setName("犬小哈" + i); user.setAge(i); user.setGender(1); users.add(user); } userService.saveBatch(users); System.out.println(String.format("总耗时:%s ms", System.currentTimeMillis() - start)); } 

真实批量插入

注意,真实业务场景下,也不可能会将 10 万多条记录组装成一条 SQL 进行批量插入,因为数据库对执行 SQL 大小是有限制的(这个数值可以自行设置),还是需要分片插入,比如取 1000 条执行一次批量插入,单元测试代码如下:

@Test    void testInsertBatch1() {        // 总耗时:6320 ms, 约 6 秒 long start = System.currentTimeMillis(); List users = new ArrayList<>(); for (int i = 0; i < 1006; i++) { User user = new User(); user.setName("犬小哈" + i); user.setAge(i); user.setGender(1); users.add(user); } // 分片插入(每 1000 条执行一次批量插入) int batchSize = 1000; int total = users.size(); // 需要执行的次数 int insertTimes = total / batchSize; // 最后一次执行需要提交的记录数(防止可能不足 1000 条) int lastSize = batchSize; if (total % batchSize != 0) { insertTimes++; lastSize = total%batchSize; } for (int j = 0; j < insertTimes; j++) { if (insertTimes == j+1) { batchSize = lastSize; } // 分片执行批量插入 userMapper.insertBatchSomeColumn(users.subList(j*batchSize, (j*batchSize+batchSize))); } System.out.println(String.format("总耗时:%s ms", System.currentTimeMillis() - start)); } 

耗时对比

方式总耗时
for 循环插入722963 ms, 约 12 分钟
savaBatch() 伪批量插入95864 ms, 约一分钟30秒左右
真实批量插入6320 ms, 约 6 秒

耗时对比非常直观,在大批量数据新增的场景下,批量插入性能最高。

结语

本小节中,我们学习了如何通过 Mybatis Plus 的 SQL 注入器实现真实的批量插入,同时最后还对比了三种不同方式插入 10 万多数据的耗时,很直观的看到在海量数据场景下,批量插入的性能是最强的。

来源地址:https://blog.csdn.net/m0_61442607/article/details/128887909

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯