当前位置: 代码网 > it编程>编程语言>Java > mybatis-plus批量插入优化方式

mybatis-plus批量插入优化方式

2024年09月25日 Java 我要评论
mybatis-plus批量插入优化背景使用的mybatisplus的批量插入方法:savebatch(),打印 sql 日志发现,底层还是一条条的 insert 语句,这显然是不行的优化之前就看到过

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>

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com