• Mybatis-Plus批量插入应该怎么用


    没有绝路,人也不会死在绝路上,大众只会迷失于路口


    mybatis-plusIService接口 默认提供 saveBatch批量插入,也是唯一一个默认批量插入,在数据量不是很大的情况下可以直接使用,但这种是一条一条执行的效率上会有一定的瓶颈,今天我们就来研究研究mybatis-plus中的批量插入。

    1. 准备测试环境

    新建一个测试表,用插入5000条数据来测试

    CREATE TABLE `users` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
      `user_name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      `email` varchar(255) DEFAULT NULL,
      `address` varchar(255) DEFAULT NULL,
      `account` varchar(255) DEFAULT NULL,
      `password` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=60204 DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    pom依赖

     <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
                <version>2.7.2version>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <version>2.7.2version>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.10version>
            dependency>
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>3.5.2version>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.13version>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    yml配置

    spring:
      application:
        name: example-server
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/my_user?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
        username: root
        password: root
      main:
        allow-circular-references: true
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    实体类

    /**
     * @description: 默认驼峰转换
     * @author: yh
     * @date: 2022/8/29
     */
    @Data
    public class Users extends Model<Users> {
        /**
         * id自增
         */
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
        private LocalDateTime createTime;
        private String userName;
        private Integer age;
        private String email;
        private String address;
        private String account;
        private String password;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    UsersMapper

    public interface UsersMapper extends BaseMapper<Users> {
    
    }
    
    • 1
    • 2
    • 3

    UsersMapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.mapper.UsersMapper">
    
    mapper>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    service接口

    public interface UsersService extends IService<Users> {
    
    }
    
    • 1
    • 2
    • 3

    service实现

    @Service
    public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
       
    }
    
    • 1
    • 2
    • 3
    • 4

    配置扫描

    @MapperScan("com.example.mapper")
    @SpringBootApplication
    public class SpringExampleApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringExampleApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    环境准备完成。

    2. saveBatch

    @RequestMapping(value = "/api")
    @RestController
    @Slf4j
    public class ExampleController {
        @Autowired
        private UsersService usersService;
    
        @RequestMapping(value = "/load", method = RequestMethod.GET)
        public void load() {
            List<Users> list = new ArrayList<>();
            for (int i = 0; i < 5000; i++) {
                Users users = new Users();
    
                users.setUserName("yy" + i);
                users.setAge(18);
                users.setEmail("123@qq.com");
                users.setAddress("临汾" + i);
                users.setAccount("account" + i);
                users.setPassword("password" + i);
                list.add(users);
            }
            long start = System.currentTimeMillis();
            usersService.saveBatch(list);
            long end = System.currentTimeMillis();
            System.out.println("5000条数据插入,耗时:" + (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    执行过程:
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    5000条数据被分成了5次执行,每次1000条,整体是一个事务

    在这里插入图片描述

    在这里插入图片描述

    耗时:48.5

    2.1 分析

    点进saveBatch方法,看看内部是怎么实现的
    在这里插入图片描述

    注意看,方法有一个事务注解,说明插入整批数据会作为一个事务进行
    在这里插入图片描述

    默认1000条一次,怪不得执行时每隔1000条会处于一个准备执行状态,等待几秒后才会往下执行(这里等待的时间就是1000个单条的insert语句执行的时间)

    再往下点是一个saveBatch接口,参数分别是插入的对象集合、插入批次数量也就是默认的1000
    在这里插入图片描述

    查看它的实现类
    在这里插入图片描述

    这里也有一个事务的注解,这是因为saveBatch是一个重载方法,插入的时候也可以指定插入批次数量调用
    在这里插入图片描述

    继续往下进入executeBatch
    在这里插入图片描述

    再往下
    在这里插入图片描述

    这里就是真正执行的方法了,idxLimit会对比DEFAULT_BATCH_SIZE和集合长度两个数中的最小数,作为批量大小,也就是说当集合长度不够1000,那么执行的时候批量大小就是集合的长度,就执行一次。

    for循环中的consumer:对应的类型是一个函数式接口,代表一个接受两个输入参数且不返回任何内容的操作符。意思是给定两个参数sqlSession、循环中当前element对象,执行一次传递过来的consumer匿名函数。也就是上边源码里的插入匿名函数。
    在这里插入图片描述

    i == indLimit时:执行一次预插入,并重新计算idxLimit的值

    ifidxLimit计算规则:当前idxLimitbatchSize(默认1000) 和 集合长度 取最小值,计算出来的结果肯定不会超过集合的长度,最后的批次时idxLimit等于集合的长度,将这个值作为下一次执行预插入的时间点。

    sqlSession.flushStatements():当有处于事务中的时候,起到一种预插入的作用,执行了这行代码之后,要插入的数据会锁定数据库的一行记录,并把数据库默认返回的主键赋值给插入的对象,这样就可以把该对象的主键赋值给其他需要的对象中去了,这里不是事务提交啊。

    最后方法执行完后@Transactional注解会默认提交事务,如果调用的方法上还有@Transactional注解,默认的事务传播类型是Propagation.REQUIRED,不会新开启事务,如果没有@Transactional注解才会新开起事务
    详细介绍请看Spring @Transactional事务管理

    3. insert循环插入

    把数据库中的数据清空,还原到空表状态

    @Service
    public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
        @Override
        public void insertList() {
            List<Users> list = new ArrayList<>();
            for (int i = 0; i < 5000; i++) {
                Users users = new Users();
    
                users.setUserName("yy" + i);
                users.setAge(18);
                users.setEmail("123@qq.com");
                users.setAddress("临汾" + i);
                users.setAccount("1" + i);
                users.setPassword("pw" + i);
                list.add(users);
            }
            long start = System.currentTimeMillis();
            for (int i = 0; i < list.size(); i++) {
                baseMapper.insert(list.get(i));
            }
            long end = System.currentTimeMillis();
            System.out.println("5000条数据插入,耗时:" + (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    5000条数据每条都是一个单独的事务。就是一条条插入,成功失败都不会互相影响
    耗时:161.2

    4. 自定义sql插入

    UsersMapper.xml

        <insert id="insertList">
            insert into users(user_name, age, email, address, account, password) values
            <foreach collection="list" item="it" separator=",">
                (#{it.userName}, #{it.age}, #{it.email}, #{it.address}, #{it.account}, #{it.password})
            foreach>
        insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    UsersMapper

     void insertList(@Param("list") List<Users> list);
    
    • 1
    @Service
    public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
        @Override
        public void insertList() {
            List<Users> list = new ArrayList<>();
            for (int i = 0; i < 5000; i++) {
                Users users = new Users();
    
                users.setUserName("yy" + i);
                users.setAge(18);
                users.setEmail("123@qq.com");
                users.setAddress("临汾" + i);
                users.setAccount("1" + i);
                users.setPassword("pw" + i);
                list.add(users);
            }
            long start = System.currentTimeMillis();
            baseMapper.insertList(list);
            long end = System.currentTimeMillis();
            System.out.println("5000条数据插入,耗时:" + (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    这里是把要插入的数据拼接在一个insert语句后面执行

    INSERT INTO users (user_name,age,email,address,account,password) 
    VALUES (?,?,?,?,?,?) , (?,?,?,?,?,?) , (?,?,?,?,?,?).....
    
    • 1
    • 2

    这种效率是最高的,但是这种需要我们在每个批量插入对应的xml中取写sql语句,有点不太符合现在提倡的免sql开发,下面介绍一下它的升级版

    5. insertBatchSomeColumn

    mybatis-plus提供了InsertBatchSomeColumn批量insert方法。通过SQL 自动注入器接口 ISqlInjector注入通用方法 SQL 语句 然后继承 BaseMapper 添加自定义方法,全局配置 sqlInjector 注入 MP 会自动将类所有方法注入到 mybatis 容器中。我们需要通过这种方式注入下。

    MySqlInjector.java

    /**
     * 自定义Sql注入
     * @author:  yh
     * @date:  2022/8/30
     */
    public class MySqlInjector extends DefaultSqlInjector {
    
        @Override
        public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
            List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
            //增加自定义方法,字段注解上不等于FieldFill.DEFAULT的字段才会插入
            methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.DEFAULT));
            return methodList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    MybatisPlusConfig.java

    @Configuration
    public class MybatisPlusConfig {
    
        @Bean
        public MySqlInjector sqlInjector() {
            return new MySqlInjector();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    自定义MyBaseMapper

    public interface MyBaseMapper <T> extends BaseMapper<T> {
    
        int insertBatchSomeColumn(List<T> entityList);
    }
    
    • 1
    • 2
    • 3
    • 4

    mapper继承的这里也改下

    public interface UsersMapper extends MyBaseMapper<Users> {
    
    }
    
    • 1
    • 2
    • 3

    插入的时候过滤字段,需要配置属性

    @Data
    public class Users extends Model<Users> {
        /**
         * id
         */
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
    
        // 插入的时候过滤这个字段,默认值就是FieldFill.DEFAULT
        @TableField(fill = FieldFill.DEFAULT)
        private LocalDateTime createTime;
    
        @TableField(fill = FieldFill.INSERT)
        private String userName;
    
        @TableField(fill = FieldFill.INSERT)
        private Integer age;
        @TableField(fill = FieldFill.INSERT)
        private String email;
        @TableField(fill = FieldFill.INSERT)
        private String address;
        @TableField(fill = FieldFill.INSERT)
        private String account;
        @TableField(fill = FieldFill.INSERT)
        private String password;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    修改一下service调用

    @Service
    public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
        @Autowired
        private SqlSessionFactory sqlSessionFactory;
        @Override
        public void insertList() {
            List<Users> list = new ArrayList<>();
            for (int i = 0; i < 5000; i++) {
                Users users = new Users();
    
                users.setUserName("yy" + i);
                users.setAge(18);
                users.setEmail("123@qq.com");
                users.setAddress("临汾" + i);
                users.setAccount("1" + i);
                users.setPassword("pw" + i);
                list.add(users);
            }
            long start = System.currentTimeMillis();
            baseMapper.insertBatchSomeColumn(list);
            long end = System.currentTimeMillis();
            System.out.println("5000条数据插入,耗时:" + (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行结果:
    在这里插入图片描述

    createTime字段被过滤掉了
    在这里插入图片描述

    在这里插入图片描述

    执行时间和上边自定义sql一样,在1秒内浮动,如果量太大几十万条建议多线程分批处理。Java 多线程分批同步数据


    到此,本章内容就介绍完啦,如果有帮助到你 欢迎点个赞👍👍👍吧!! 有问题评论区交流
  • 相关阅读:
    【Excel】WPS单元格快速转换表格字母大小写
    如何优化 Bash 脚本的执行效率?
    中国帘子布市场投资前景分析及供需格局研究预测报告
    Clickhouse—DDL 操作
    家政管理系统设计与实现
    info文档与man手册
    Spring Cloud Gateway 远程代码执行漏洞(CVE-2022-22947)
    mac的终端显示分支名称?mac的终端和idea中的terminal同时修改
    Linux学习
    Java并发编程--变量可见性、避免指令重排,还得是用它
  • 原文地址:https://blog.csdn.net/weixin_43847283/article/details/126632026