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>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论