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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 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文件。

21.png

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

20.png

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

19.png

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

18.png

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


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);
    }

执行日志:

17.png

显然,这里每次执行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请求,而不是说的事物提交)

16.png

小结:

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默认可以注入的方法如下,大家也可以参考其实现自己扩展:

15.png


默认注入器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个注入方法。

14.png


1.AlwaysUpdateSomeColumnById 根据Id更新每一个字段,全量更新不忽略null字段,解决mybatis-plus中updateById默认会自动忽略实体中null值字段不去更新的问题。

2.InsertBatchSomeColumn 真实批量插入,通过单SQL的insert语句实现批量插入

3.DeleteByIdWithFill 带自动填充的逻辑删除,比如自动填充更新时间、操作人

4.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://blog.csdn.net/weixin_42506706/article/details/113301248


四、通过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);
    }

执行结果:

13.png

可以看到已经实现单条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);
    }


执行结果:

11.png

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


总结


本文主要介绍了Mybatis-Plus中如何通过SQL注入器实现真正的批量插入。主要掌握如下内容:

1、了解Mybatis-Plus中SQL注入器有什么作用,如何去进行扩展。

2、默认的4个扩展方法各自的作用。

3、默认的saveBatch批量新增和通过insertBatchSomeColumn实现的批量新增的底层实现原理的区别,为什么insertBatchSomeColumn性能更好以及存在哪些弊端。

4、为insertBatchSomeColumn添加分批处理机制,避免批量插入的insert语句过长问题。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
15天前
|
SQL XML Java
mybatis实现动态sql
MyBatis的动态SQL功能为开发人员提供了强大的工具来应对复杂的查询需求。通过使用 `<if>`、`<choose>`、`<foreach>`等标签,可以根据不同的条件动态生成SQL语句,从而提高代码的灵活性和可维护性。本文详细介绍了动态SQL的基本用法和实际应用示例,希望对您在实际项目中使用MyBatis有所帮助。
46 11
|
2月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
2月前
|
SQL 安全 前端开发
Web学习_SQL注入_联合查询注入
联合查询注入是一种强大的SQL注入攻击方式,攻击者可以通过 `UNION`语句合并多个查询的结果,从而获取敏感信息。防御SQL注入需要多层次的措施,包括使用预处理语句和参数化查询、输入验证和过滤、最小权限原则、隐藏错误信息以及使用Web应用防火墙。通过这些措施,可以有效地提高Web应用程序的安全性,防止SQL注入攻击。
64 2
|
3月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
61 10
|
4月前
|
SQL XML Java
mybatis复习03,动态SQL,if,choose,where,set,trim标签及foreach标签的用法
文章介绍了MyBatis中动态SQL的用法,包括if、choose、where、set和trim标签,以及foreach标签的详细使用。通过实际代码示例,展示了如何根据条件动态构建查询、更新和批量插入操作的SQL语句。
mybatis复习03,动态SQL,if,choose,where,set,trim标签及foreach标签的用法
|
4月前
|
SQL 安全 数据库
惊!Python Web安全黑洞大曝光:SQL注入、XSS、CSRF,你中招了吗?
在数字化时代,Web应用的安全性至关重要。许多Python开发者在追求功能时,常忽视SQL注入、XSS和CSRF等安全威胁。本文将深入剖析这些风险并提供最佳实践:使用参数化查询预防SQL注入;通过HTML转义阻止XSS攻击;在表单中加入CSRF令牌增强安全性。遵循这些方法,可有效提升Web应用的安全防护水平,保护用户数据与隐私。安全需持续关注与改进,每个细节都至关重要。
145 5
|
4月前
|
SQL 安全 数据库
深度揭秘:Python Web安全攻防战,SQL注入、XSS、CSRF一网打尽!
在Web开发领域,Python虽强大灵活,却也面临着SQL注入、XSS与CSRF等安全威胁。本文将剖析这些常见攻击手段,并提供示例代码,展示如何利用参数化查询、HTML转义及CSRF令牌等技术构建坚固防线,确保Python Web应用的安全性。安全之路永无止境,唯有不断改进方能应对挑战。
89 5
|
4月前
|
SQL 安全 数据安全/隐私保护
Python Web安全大挑战:面对SQL注入、XSS、CSRF,你准备好了吗?
在构建Python Web应用时,安全性至关重要。本文通过三个真实案例,探讨了如何防范SQL注入、XSS和CSRF攻击。首先,通过参数化查询替代字符串拼接,防止SQL注入;其次,利用HTML转义机制,避免XSS攻击;最后,采用CSRF令牌验证,保护用户免受CSRF攻击。这些策略能显著增强应用的安全性,帮助开发者应对复杂的网络威胁。安全是一个持续的过程,需不断学习新知识以抵御不断变化的威胁。
134 1
|
4月前
|
SQL 安全 数据库
Python Web开发者必看!SQL注入、XSS、CSRF全面解析,守护你的网站安全!
在Python Web开发中,构建安全应用至关重要。本文通过问答形式,详细解析了三种常见Web安全威胁——SQL注入、XSS和CSRF,并提供了实用的防御策略及示例代码。针对SQL注入,建议使用参数化查询;对于XSS,需对输出进行HTML编码;而防范CSRF,则应利用CSRF令牌。通过这些措施,帮助开发者有效提升应用安全性,确保网站稳定运行。
66 1
|
4月前
|
SQL 安全 数据库
深度揭秘:Python Web安全攻防战,SQL注入、XSS、CSRF一网打尽!
在Web开发领域,Python虽强大灵活,但安全挑战不容小觑。本文剖析Python Web应用中的三大安全威胁:SQL注入、XSS及CSRF,并提供防御策略。通过示例代码展示如何利用参数化查询、HTML转义与CSRF令牌构建安全防线,助您打造更安全的应用。安全是一场持久战,需不断改进优化。
60 3