Mybatis-Plus通过SQL注入器实现批量插入的实践

 更新时间:2022年08月11日 10:24:35   作者:斗者_2013  
本文主要介绍了Mybatis-Plus通过SQL注入器实现批量插入的实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

批量插入是实际工作中常见的一个功能,mysql支持一条sql语句插入多条数据。但是Mybatis-Plus中默认提供的saveBatch方法并不是真正的批量插入,而是遍历实体集合每执行一次insert语句插入一条记录。相比批量插入,性能上显然会差很多。
今天谈一下,在Mybatis-Plus中如何通过SQL注入器实现真正的批量插入。

一、mysql批量插入的支持

insert批量插入的语法支持:

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

二、Mybatis-Plus默认saveBatch方法解析

1、测试工程建立

测试的数据表:

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

在IDEA中配置好数据库连接,并安装好MybatisX-Generator插件,生成对应表的model、mapper、service、xml文件。

生成的文件推荐保存在工程目录下,generator目录下。先生成文件,用户根据自己的需要,再将文件移动到指定目录,这样避免出现文件覆盖。

生成实体的配置选项,这里我勾选了Lombok和Mybatis-Plus3,生成的类更加优雅。

移动生成的文件到对应目录:

由于都是生成的代码,这里就不补充代码了。

2、默认批量插入saveBatch方法测试

    @Test
    public void testBatchInsert() {
        System.out.println("----- batch insert method test ------");
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("test");
            user.setAge(13);
            user.setEmail("101@qq.com");
            list.add(user);
        }
        userService.saveBatch(list);
    }

执行日志:

显然,这里每次执行insert操作,都只插入了一条数据。

3、saveBatch方法实现分析

//批量保存的方法,做了分批请求处理,默认一次处理1000条数据
default boolean saveBatch(Collection<T> entityList) {
    return this.saveBatch(entityList, 1000);
}

//用户也可以自己指定每批处理的请求数量
boolean saveBatch(Collection<T> entityList, int batchSize);
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
    Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
    return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
        int size = list.size();
        int idxLimit = Math.min(batchSize, size);
        int i = 1;

        for(Iterator var7 = list.iterator(); var7.hasNext(); ++i) {
            E element = var7.next();
            consumer.accept(sqlSession, element);
            //每次达到批次数,sqlSession就刷新一次,进行数据库请求,生成Id
            if (i == idxLimit) {
                sqlSession.flushStatements();
                idxLimit = Math.min(idxLimit + batchSize, size);
            }
        }

    });
}

我们将批次数设置为3,用来测试executeBatch的处理机制。

    @Test
    public void testBatchInsert() {
        System.out.println("----- batch insert method test ------");
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("test");
            user.setAge(13);
            user.setEmail("101@qq.com");
            list.add(user);
        }
        //批次数设为3,用来测试
        userService.saveBatch(list,3);
    }

执行结果,首批提交的请求,已经生成了id,还没有提交的id为null。
(这里的提交是sql请求,而不是说的事物提交)

小结:
Mybatis-Plus中默认的批量保存方法saveBatch,底层是通过sqlSession.flushStatements()将一个个单条插入的insert语句分批次进行提交。
相比遍历集合去调用userMapper.insert(entity),执行一次提交一次,saveBatch批量保存有一定的性能提升,但从sql层面上来说,并不算是真正的批量插入。

补充:

遍历集合单次提交的批量插入。

 @Test
    public void forEachInsert() {
        System.out.println("forEachInsert 插入开始========");
        long start = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            userMapper.insert(list.get(i));
        }
        System.out.println("foreach 插入耗时:"+(System.currentTimeMillis()-start));
    }

三、Mybatis-plus中SQL注入器介绍

SQL注入器官方文档:https://baomidou.com/pages/42ea4a/

1.sqlInjector介绍

SQL注入器sqlInjector 用于注入 ISqlInjector 接口的子类,实现自定义方法注入。
参考默认注入器 DefaultSqlInjector

Mybatis-plus默认可以注入的方法如下,大家也可以参考其实现自己扩展:

默认注入器DefaultSqlInjector的内容:

public class DefaultSqlInjector extends AbstractSqlInjector {
    public DefaultSqlInjector() {
    }

    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        //注入通用的dao层接口的操作方法
        return (List)Stream.of(new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage()).collect(Collectors.toList());
    }
}

2.扩展中提供的4个可注入方法实现

目前在mybatis-plus的扩展插件中com.baomidou.mybatisplus.extension,给我们额外提供了4个注入方法。

  • AlwaysUpdateSomeColumnById 根据Id更新每一个字段,全量更新不忽略null字段,解决mybatis-plus中updateById默认会自动忽略实体中null值字段不去更新的问题。
  • InsertBatchSomeColumn 真实批量插入,通过单SQL的insert语句实现批量插入
  • DeleteByIdWithFill 带自动填充的逻辑删除,比如自动填充更新时间、操作人
  • Upsert 更新or插入,根据唯一约束判断是执行更新还是删除,相当于提供insert on duplicate key update支持
insert into t_name (uid, app_id,createTime,modifyTime)
values(111, 1000000,'2017-03-07 10:19:12','2017-03-07 10:19:12')
on duplicate key update uid=111, app_id=1000000, 
createTime='2017-03-07 10:19:12',modifyTime='2017-05-07 10:19:12'

mysql在存在主键冲突或者唯一键冲突的情况下,根据插入策略不同,一般有以下三种避免方法。

  • insert ignore
  • replace into
  • insert on duplicate key update

这里不展开介绍,大家可以自行查看:https://www.jb51.net/article/194579.htm

四、通过SQL注入器实现真正的批量插入

通过SQL注入器sqlInjector 增加批量插入方法InsertBatchSomeColumn的过程如下:

1.继承DefaultSqlInjector扩展自定义的SQL注入器

代码如下:

/**
 * 自定义Sql注入
 */
public class MySqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
         //更新时自动填充的字段,不用插入值
         methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
        return methodList;
    }
}

2.将自定义的SQL注入器注入到Mybatis容器中

代码如下:

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MySqlInjector sqlInjector() {
        return new MySqlInjector();
    }
}

3.继承 BaseMapper 添加自定义方法

public interface CommonMapper<T> extends BaseMapper<T> {
    /**
     * 全量插入,等价于insert
     * @param entityList
     * @return
     */
    int insertBatchSomeColumn(List<T> entityList);
}

4.Mapper层接口继承新的CommonMapper

public interface UserMapper extends CommonMapper<User> {

}

5.单元测试

 @Test
    public void testBatchInsert() {
        System.out.println("----- batch insert method test ------");
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("test");
            user.setAge(13);
            user.setEmail("101@qq.com");
            list.add(user);
        }
        userMapper.insertBatchSomeColumn(list);
    }

执行结果:

可以看到已经实现单条insert语句支持数据的批量插入。

注意⚠️:
默认的insertBatchSomeColumn实现中,并没有类似saveBatch中的分配提交处理,
这就存在一个问题,如果出现一个非常大的集合,就会导致最后组装提交的insert语句的长度超过mysql的限制。

6.insertBatchSomeColumn添加分批处理机制

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private UserMapper userMapper;

    /**
     * 采用insertBatchSomeColumn重写saveBatch方法,保留分批处理机制
     * @param entityList
     * @param batchSize
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean saveBatch(Collection<User> entityList, int batchSize) {
        try {
            int size = entityList.size();
            int idxLimit = Math.min(batchSize, size);
            int i = 1;
            //保存单批提交的数据集合
            List<User> oneBatchList = new ArrayList<>();
            for(Iterator<User> var7 = entityList.iterator(); var7.hasNext(); ++i) {
                User element = var7.next();
                oneBatchList.add(element);
                if (i == idxLimit) {
                    userMapper.insertBatchSomeColumn(oneBatchList);
                    //每次提交后需要清空集合数据
                    oneBatchList.clear();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
            }
        }catch (Exception e){
            log.error("saveBatch fail",e);
            return false;
        }
        return  true;
    }

更好的实现是继承ServiceImpl实现类,自己扩展通用的服务实现类,在其中重写通用的saveBatch方法,这样就不用在每一个服务类中都重写一遍saveBatch方法。

单元测试:

    @Test
    public void testBatchInsert() {
        System.out.println("----- batch insert method test ------");
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("test");
            user.setAge(13);
            user.setEmail("101@qq.com");
            list.add(user);
        }
        //批次数设为3,用来测试
        userService.saveBatch(list,3);
    }

执行结果:

分4次采用insert批量新增,符合我们的结果预期。

总结

本文主要介绍了Mybatis-Plus中如何通过SQL注入器实现真正的批量插入。主要掌握如下内容:
1、了解Mybatis-Plus中SQL注入器有什么作用,如何去进行扩展。
2、默认的4个扩展方法各自的作用。
3、默认的saveBatch批量新增和通过insertBatchSomeColumn实现的批量新增的底层实现原理的区别,为什么insertBatchSomeColumn性能更好以及存在哪些弊端。
4、为insertBatchSomeColumn添加分批处理机制,避免批量插入的insert语句过长问题。

到此这篇关于Mybatis-Plus通过SQL注入器实现批量插入的实践的文章就介绍到这了,更多相关Mybatis-Plus SQL注入器批量插入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解MyBatis resultType与resultMap中的几种返回类型

    详解MyBatis resultType与resultMap中的几种返回类型

    本文主要介绍了MyBatis resultType与resultMap中的几种返回类型,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Java安全之Tomcat6 Filter内存马问题

    Java安全之Tomcat6 Filter内存马问题

    这篇文章主要介绍了Java安全之Tomcat6 Filter内存马,通过本文探讨下Tomcat6与Tomcat8之间的区别,主要看下tomcat6和tomcat8之间createFilterChain不相同的地方 看到ApplicationFilterFactory#createFilterChain,需要的朋友可以参考下
    2022-10-10
  • SpringBoot在IDEA中实现热部署的步骤

    SpringBoot在IDEA中实现热部署的步骤

    这篇文章主要介绍了SpringBoot在IDEA中实现热部署的步骤,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下
    2020-11-11
  • spring cloud学习教程之config修改配置详解

    spring cloud学习教程之config修改配置详解

    这篇文章主要给大家介绍了关于spring cloud学习教程之config修改配置的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-09-09
  • java关于持久层面试题目整理

    java关于持久层面试题目整理

    在本篇文章里小编给大家分享的是一篇关于java关于持久层面试题目整理内容,需要的朋友们可以学习下。
    2020-03-03
  • Spring超详细讲解面向对象到面向切面

    Spring超详细讲解面向对象到面向切面

    面向对象编程是一种编程方式,此编程方式的落地需要使用“类”和 “对象”来实现,所以,面向对象编程其实就是对 “类”和“对象” 的使用,面向切面编程,简单的说,就是动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程
    2022-08-08
  • volatile可见性的一些认识和论证

    volatile可见性的一些认识和论证

    volatile的关键词的使用在JVM内存模型中已是老生常谈了,这篇文章主要结合自己对可见性的一些认识和一些直观的例子来谈谈volatile,感兴趣的朋友一起看看吧
    2017-08-08
  • SpringBoot下使用自定义监听事件的流程分析

    SpringBoot下使用自定义监听事件的流程分析

    事件机制是Spring的一个功能,目前我们使用了SpringBoot框架,所以记录下事件机制在SpringBoot框架下的使用,同时实现异步处理,这篇文章主要介绍了SpringBoot下使用自定义监听事件,需要的朋友可以参考下
    2023-08-08
  • No ‘Access-Control-Allow-Origin‘ header is present跨域及解决

    No ‘Access-Control-Allow-Origin‘ header is&nb

    这篇文章主要介绍了No ‘Access-Control-Allow-Origin‘ header is present跨域及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • SSM使用mybatis分页插件pagehepler实现分页示例

    SSM使用mybatis分页插件pagehepler实现分页示例

    本篇文章主要介绍了SSM使用mybatis分页插件pagehepler实现分页示例,使用分页插件的原因,简化了sql代码的写法,实现较好的物理分页,非常具有实用价值,需要的朋友可以参考下
    2018-03-03

最新评论