mybatis-plus批量插入优化方式

 更新时间:2024年09月25日 15:48:49   作者:hogenlaw  
MyBatis-Plus的saveBatch()方法默认是单条插入,通过在JDBC URL添加rewriteBatchedStatements=true参数启用批量插入,官方提供的sql注入器可自定义方法,如InsertBatchSomeColumn实现真批量插入,但存在单次插入数据量过大问题,可通过分批插入优化,避免超出MySQL限制

mybatis-plus批量插入优化

背景

使用的mybatisplus的批量插入方法:

saveBatch(),打印 sql 日志发现,底层还是一条条的 insert 语句,这显然是不行的

优化

之前就看到过网上都在说在jdbc的url路径上加上rewriteBatchedStatements=true 参数mysql底层才能开启真正的批量插入模式。但是我已经添加了

通过查阅相关文档后,发现mybatisPlus提供了sql注入器,我们可以自定义方法来满足业务的实际开发需求。

sql 注入器官网:https://baomidou.com/guides/sql-injector/

mybatis-plus -core 核心包提供了基本的增删查改注入器,在批量插入数据这里显然不够,所以可以看到在 mybaits-plus-extension 包下还额外提供了批量插入的可注入方法


  • AlwaysUpdateSomeColumnById: 根据Id更新每一个字段,全量更新不忽略null字段,解决mybatis-plus中updateById默认会自动忽略实体中null值字段不去更新的问题;
  • InsertBatchSomeColumn: 真实批量插入,通过单SQL的insert语句实现批量插入;
  • Upsert:更新or插入,根据唯一约束判断是执行更新还是删除,相当于提供insert on duplicate key update支持。

我们只需要把这个方法添加进我们的sql注入器即可。

config包新增如下两个配置

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

原先的 mapper 是这么写的

public interface UserMapper extends BaseMapper<User> {
}

我们新增了 InsertBatchSomeColumn 方法,需要重新定义一个 BaseMapper

public interface CommonMapper<T> extends BaseMapper<T> {
    /**
     * 真正的批量插入
     * @param entityList
     * @return
     */
    int insertBatchSomeColumn(List<T> entityList);
}
public interface UserMapper extends CommonMapper<User> {
}

优化后的接口就对了,sql 显示确实是 批量插入的语句

新的问题

上面虽然实现了真正意义上的sql层面的批量插入。

但是,到这里并没有结束,mybatisPlus官方提供的 insertBatchSomeColumn 方法不支持分批插入,也就是有多少直接全部一次性插入,这就可能会导致最后的 sql 拼接语句特别长,超出了mysql 的限制, 可能会报下面这个错,

说你这个包太大了。可以通过设置 max_allowed_packet 来改变包大小。

当然我们可以通过下面的语句查询当前的配置大小:

select @@max_allowed_packet;

我这里就使用 sql 语句把值修改为 64M:

set global max_allowed_packet = 1024*1024*64;

但是改这个配置治标不治本,能不能从代码层面对拼接的 sql 语句做个优化呢,限制不要太大,于是我们还要实现一个类似于saveBatch 分批的批量插入方法。

分批插入

模仿原来的saveBatch方法:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @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> it = entityList.iterator(); it.hasNext(); ++i) {
                User element = it.next();
                oneBatchList.add(element);
                if (i == idxLimit) {
                    baseMapper.insertBatchSomeColumn(oneBatchList);
                    //每次提交后需要清空集合数据
                    oneBatchList.clear();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
            }
        } catch (Exception e) {
            log.error("saveBatch fail", e);
            return false;
        }
        return true;
    }
}

从下面结果可以看到,最终的 sql 分成了两个批次,这样的话 sql 语句就不会太长

springboot3整合mybaits-plus

这里就简单粘贴一下pom文件,注意 用mybatis-plus-spring-boot3-starter 这个依赖,不是用 mybatis-plus-spring-boot-starter ,不然报错

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example.springbootV3</groupId>
	<artifactId>springbootV3</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springbootV3</name>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>3.1.3</version>
		</dependency>
		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
			<version>3.5.5</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
spring.datasource.url=jdbc:mysql://192.168.133.128:3306/wxpay?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:/com/example/demo/**/*Mapper.xml

mybatis 插入后返回主键

如果是使用了 mybatis-plus,可以直接使用封装好的 insert 方法,通过 service直接调用

userService.save(user);
Integer id = user.getId();

如果直接使用 mybatis,有下面两种方法。

  • 一种是 在 insert 标签加入 useGeneratedKeys="true" keyProperty="id" 属性,
  • 一种是 selectKey 标签
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <insert id="saveReturnPK1" parameterType="com.example.demo.entity.User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO `wxpay`.`t_user`(`name`, age) VALUES(#{name}, #{age})
    </insert>
    <insert id="saveReturnPK2" parameterType="com.example.demo.entity.User">
        <selectKey keyProperty="id" resultType="int" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO `wxpay`.`t_user`(`name`, age) VALUES(#{name}, #{age})
    </insert>
</mapper>
userMapper.saveReturnPK1(user);
Integer id = user.getId();

mybaits-plus 代码生成器

mybatis-plus新版本通过 builder 模式可以快速生成你想要的代码,快速且优雅,官网在这里

public class CodeGenerator {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://192.168.133.128:3306/wxpay", "root", "root")
                .globalConfig(builder -> {
                    builder.author("guang") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .outputDir("D://MP//"); // 指定输出目录
                })
                .dataSourceConfig(builder ->
                        builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                            int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                            if (typeCode == Types.SMALLINT) {
                                // 自定义类型转换
                                return DbColumnType.INTEGER;
                            }
                            return typeRegistry.getColumnType(metaInfo);
                        })
                )
                .packageConfig(builder ->
                        builder
                                .moduleName("com.example.demo") // 设置父包模块名
                                .entity("entity")
                                .mapper("mapper")
                                .service("service")
                                .serviceImpl("service.impl")
                                .xml("mapper.xml")
                                .pathInfo(Collections.singletonMap(OutputFile.xml, "D://MP//")) // 设置mapperXml生成路径
                )
                .strategyConfig(builder ->
                        builder.addInclude("t_user") // 设置需要生成的表名
                                .addTablePrefix("t_", "c_") // 设置过滤表前缀
                                .serviceBuilder().formatServiceFileName("%sService")
                )
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

注意需要引入Freemarker 依赖,不然报错

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java objectUtils 使用可能会出现的问题

    java objectUtils 使用可能会出现的问题

    这篇文章主要介绍了java objectUtils 使用可能会出现的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • java编程实现杨辉三角两种输出结果实例代码

    java编程实现杨辉三角两种输出结果实例代码

    这篇文章主要介绍了java编程实现杨辉三角两种输出结果实例代码,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • springboot数据库密码加密的配置方法

    springboot数据库密码加密的配置方法

    这篇文章主要给大家介绍了关于springboot数据库密码加密的配置方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Java instanceof用法详解及实例代码

    Java instanceof用法详解及实例代码

    这篇文章主要介绍了Java instanceof用法详解及实例代码的相关资料,需要的朋友可以参考下
    2017-02-02
  • servlet实现用户登录小程序

    servlet实现用户登录小程序

    这篇文章主要为大家详细介绍了servlet实现用户登录的小程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • Java异常跟踪栈定义与用法示例

    Java异常跟踪栈定义与用法示例

    这篇文章主要介绍了Java异常跟踪栈定义与用法,结合具体实例形式分析了异常处理栈的概念、原理及相关使用技巧,需要的朋友可以参考下
    2018-05-05
  • 提交gRPC-spring-boot-starter项目bug修复的pr说明

    提交gRPC-spring-boot-starter项目bug修复的pr说明

    这篇文章主要介绍了这篇文章主要为大家介绍了gRPC-spring-boot-starter项目提交bug修复的pr的原因说明,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-02-02
  • MyBatis-Plus 与Druid 数据源操作

    MyBatis-Plus 与Druid 数据源操作

    SpringBoot框架集成MyBatis-Plus和Druid数据源,简化了数据操作与监控,MyBatis-Plus作为MyBatis的增强工具,自动实现CRUD操作,减少手写SQL,提供分页、逻辑删除等功能,本文介绍MyBatis-Plus & Druid 数据源总结,感兴趣的朋友一起看看吧
    2024-09-09
  • SpringCache轻松启用Redis缓存的全过程

    SpringCache轻松启用Redis缓存的全过程

    Spring Cache是Spring提供的一种缓存抽象机制,旨在通过简化缓存操作来提高系统性能和响应速度,本文将给大家详细介绍SpringCache如何轻松启用Redis缓存,文中有详细的代码示例供大家参考,需要的朋友可以参考下
    2024-07-07
  • Java throw Exception实现异常转换

    Java throw Exception实现异常转换

    这篇文章主要介绍了Java throw Exception实现异常转换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04

最新评论