首页 > 数据库 >Mybatis-Plus通过SQL注入器实现真正的批量插入

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

时间:2022-12-07 17:35:42浏览次数:58  
标签:insert 批量 saveBatch 插入 Plus user SQL Mybatis new


文章目录

  • ​​前言​​
  • ​​一、mysql批量插入的支持​​
  • ​​二、Mybatis-Plus默认saveBatch方法解析​​
  • ​​1、测试工程建立​​
  • ​​2、默认批量插入saveBatch方法测试​​
  • ​​3、saveBatch方法实现分析​​
  • ​​三、Mybatis-plus中SQL注入器介绍​​
  • ​​1.sqlInjector介绍​​
  • ​​2.扩展中提供的4个可注入方法实现​​
  • ​​四、通过SQL注入器实现真正的批量插入​​
  • ​​1.继承DefaultSqlInjector扩展自定义的SQL注入器​​
  • ​​2.将自定义的SQL注入器注入到Mybatis容器中​​
  • ​​3.继承 BaseMapper 添加自定义方法​​
  • ​​4.Mapper层接口继承新的CommonMapper​​
  • ​​5.单元测试​​
  • ​​6.insertBatchSomeColumn添加分批处理机制​​
  • ​​总结​​

前言

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


一、mysql批量插入的支持

insert批量插入的语法支持:

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');

二、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文件。

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


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

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


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

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


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

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


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

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("[email protected]");
list.add(user);
}
userService.saveBatch(list);
}

执行日志:

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


显然,这里每次执行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("[email protected]");
list.add(user);
}
//批次数设为3,用来测试
userService.saveBatch(list,3);
}

执行结果,首批提交的请求,已经生成了id,还没有提交的id为null。

(这里的提交是sql请求,而不是说的事物提交)

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


小结:

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

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

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

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

  • ​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

四、通过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("[email protected]");
list.add(user);
}
userMapper.insertBatchSomeColumn(list);
}

执行结果:

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


可以看到已经实现单条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("[email protected]");
list.add(user);
}
//批次数设为3,用来测试
userService.saveBatch(list,3);
}

执行结果:

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


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


总结

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


标签:insert,批量,saveBatch,插入,Plus,user,SQL,Mybatis,new
From: https://blog.51cto.com/u_15905482/5919835

相关文章

  • mybatis-plus雪花算法增强:idworker
    文章目录​​前言​​​​一、官网​​​​二、默认实现的弊端​​​​三、mybatis-plus中datacenterId和workerId的默认生成规则​​​​四、idworker介绍​​​​五、idwo......
  • Mybatis-Plus中updateById方法不能更新空值问题
    问题描述在Mybatis-Plus中调用updateById方法进行数据更新默认情况下是不能更新空值字段的。而在实际开发过程中,往往会遇到需要将字段值更新为空值的情况。那么如果让Mybat......
  • mybatis-plus异常记录:org.apache.ibatis.binding.BindingException Invalid bound st
    问题描述我们在使用mybatis或mybatis-plus作为持久化框架的时候,通过dao层接口调用xml中配置好的sql时,常常会遇到​​org.apache.ibatis.binding.BindingExceptionInvalidb......
  • Centos7上使用yum安装mysql8.x
    文章目录​​前言​​​​一、官方文档​​​​二、安装步骤​​​​1.安装MySQLYumRepository​​​​2.选择要安装的版本​​​​3.检查rpm的签名​​​​4.安装Mysql​......
  • MYSQL5.7实现递归查询
    根据父id查出所有子级,包括子级的子级,包括自身的idsys_tenant_company_relation为关联表,company_id为子id,parent_company_id为父idSELECTDATA.*FROM(......
  • SQLServer日期格式化
    SqlServer中一个非常强大的日期格式化函数SelectCONVERT(varchar(100),GETDATE(),0):0516200610:57AMSelectCONVERT(varchar(100),GETDATE(),1):05/1......
  • docker 部署 rabbitmq(持久化) 和postgresql redis mysql
    rabbitmq:dockerrun-d--hostname=rabbitmq--restart=always-eRABBITMQ_DEFAULT_USER=admin-eRABBITMQ_DEFAULT_PASS=admin--name=rabbitmq-p5672:5672-p15672......
  • mysql两阶段提交
    1.log写入机制1.1 binlog的写入机制事务执行过程中,先把日志写到binlogcache,事务提交的时候,再把binlogcache写到binlog文件中。1.2 redolog的写入机制1.2.1.存在r......
  • Mysql GTID
    GTID的全称是GlobalTransactionIdentifier,也就是全局事务ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。它由两部分组成,格式是:GTID=server_uuid:gno等价于,......
  • mysql 学习
    mysql学习解决冲突使用反引号对键进行标识键值对键``值''mysql能存储的数据类型INT整型DECIMAL浮点型VARCHAR(params)字符型BLOB二进制DATE日期TIMESTA......