简单的mybatis batch插入批处理
1.需求
公司的权限管理功能有一个岗位关联资源的分配操作,如果新增一个岗位,有时候需要将资源全部挂上去,原有的是for循环插入资源信息,发现有时候执行速度过慢,所以此处想修改为批处理模式进行处理,提高一下效率优化使用感受。此处简单记录一下批处理操作步骤,方便后续学习复习使用。
2. 具体实现步骤
主要就是引入mybatis-plus包,自己对应服务的数据库包,数据库驱动包,连接池包等基础环境包。引入包后编写mybatis-plus配置文件,数据库连接配置文件。将配置文件配置好后则编写数据库对应实体以及对应的删除新增mapper类,最后则是编写测试类即可。
我们本次测试以单条插入以及批量插入进行对比,插入没有对xml中使用<foreach>循环拼接sql
形式进行对比,这种形式数据量少的情况下效率与批量插入效率相当,但是如果数据量过大数据库会有条数限制,oracle条数限制1000,而mysql则是需要修改数据库的配置文件 my.ini
中的 max_allowed_packet
参数,一般情况是不允许过长的,所以数据量过大我们不考虑这种拼接sql的处理方式。
2.1 具体引入坐标
我在测试demo项目中引入坐标如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.git</groupId>
<artifactId>docker-hello</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mybatis-plus.version>3.3.0</mybatis-plus.version>
<fastjson.version>1.2.83</fastjson.version>
<druid.version>1.2.4</druid.version>
<hutool.version>5.5.7</hutool.version>
<lombok.version>1.18.6</lombok.version>
<mapstruct.version>1.4.1.Final</mapstruct.version>
<swagger.version>3.0.0</swagger.version>
<elasticjob.version>3.0.0-RC1</elasticjob.version>
<druid.version>1.2.4</druid.version>
<poi-tl.version>1.9.1</poi-tl.version>
<poi.version>4.1.2</poi.version>
<easyexcel.version>2.2.8</easyexcel.version>
</properties>
<!-- springboot dependency -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2日志使用包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
<!-- 测试包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 批处理测试使用包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- package -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 yml配置文件
测试demo项目的yml配置文件内容如下:
server:
port: 8088
spring:
application:
name: docker-hello # 应用程序名称,用于 Spring Cloud 的服务发现和服务注册
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 数据源类型,这里使用的是 Druid 数据源
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 驱动类名
url: jdbc:mysql://192.168.138.129:3306/test?useUnicode=true&characterEncoding=utf-8 # 数据库连接 URL
username: root # 数据库用户名
password: 101022 # 数据库密码
redis:
database: 0 # Redis 数据库索引,默认为 0
host: 192.168.138.129 # Redis 服务器的 IP 地址
port: 6379 # Redis 服务器的端口号
timeout: 20000 # Redis 连接超时时间,单位为毫秒
# springboot2.x以上如此配置,由于2.x的客户端是lettuce
lettuce:
pool:
max-active: 8 # 最大活动连接数,默认为 8
min-idle: 0 # 最小空闲连接数,默认为 0
max-idle: 8 # 最大空闲连接数,默认为 8
max-wait: 10000ms # 获取连接的最大等待时间,默认为 10000 毫秒
# mybatis plus配置
mybatis-plus:
# 扫描 mapper.xml 文件位置
mapper-locations: classpath*:/mappers/*Mapper.xml
# 别名类文件夹位置
type-aliases-package: cn.git.entity
# 基本配置
configuration:
# 驼峰模式
map-underscore-to-camel-case: true
# 二级缓存
cache-enabled: false
2.3 实体以及mapper
对应实体类内容如下:
package cn.git.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* @description: 产品表
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-09-24
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("product")
public class Product {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@TableField("name")
private String name;
@TableField("rate")
private BigDecimal rate;
@TableField("amount")
private BigDecimal amount;
@TableField("raised")
private BigDecimal raised;
@TableField("cycle")
private Integer cycle;
@TableField("end_Time")
private String endTime;
}
对应的mapper内容如下:
package cn.git.mapper;
import cn.git.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @description: 产品mapper
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-09-24
*/
public interface ProductMapper extends BaseMapper<Product> {
}
数据库建测试产品表使用建表语句如下:
CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` varchar(32) NOT NULL,
`name` varchar(20) DEFAULT NULL,
`rate` double DEFAULT NULL,
`amount` double DEFAULT NULL,
`raised` double DEFAULT NULL,
`cycle` int(11) DEFAULT NULL,
`end_Time` char(10) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
2.4 编写测试类
我们主要测试批处理执行过程,观察执行过程与普通任务foreach执行的时间差别,所以此处直接在controller中调用测试,具体的controller内容如下:
package cn.git.controller;
import cn.git.entity.Product;
import cn.git.mapper.ProductMapper;
import cn.hutool.core.util.IdUtil;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
/**
* @description: mybatis批量处理controller
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-09-24
*/
@RestController
@RequestMapping("/batch")
public class BatchController {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Autowired
private ProductMapper productMapper;
/**
* 批量插入
*
* @return
*/
@GetMapping("/add/product")
public String addProduct(){
// 插入100000
long start = System.currentTimeMillis();
// 获取SqlSession,并开启批量执行模式
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
ProductMapper productMapper = session.getMapper(ProductMapper.class);
for (int i = 0; i < 500000; i++) {
Product product = new Product();
product.setId(IdUtil.simpleUUID());
product.setName("product" + i);
product.setRate(new BigDecimal(i));
product.setAmount(new BigDecimal(i));
product.setRaised(new BigDecimal(i));
product.setCycle(i);
product.setEndTime("2024-09-24");
productMapper.insert(product);
// 测试异常回滚,可去除
if (i == 99) {
throw new RuntimeException("报错啦");
}
}
System.out.println("开始批量提交commit");
session.commit();
} catch (Exception e) {
// 事务回滚
session.rollback();
e.printStackTrace();
return "插入失败";
} finally {
// 清除缓存, 关闭session
session.clearCache();
session.close();
}
long end = System.currentTimeMillis();
// 打印用时多少秒
System.out.println("用时:" + (end - start) / 1000 + "秒");
return "批量插入完成";
}
/**
* 单条插入
*
* @return
*/
@GetMapping("/add/product2")
public String addProduct2(){
// 插入100000
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Product product = new Product();
product.setId(IdUtil.simpleUUID());
product.setName("product" + i);
product.setRate(new BigDecimal(i));
product.setAmount(new BigDecimal(i));
product.setRaised(new BigDecimal(i));
product.setCycle(i);
product.setEndTime("2024-09-24");
productMapper.insert(product);
}
long end = System.currentTimeMillis();
// 打印用时多少秒
System.out.println("用时:" + (end - start) / 1000 + "秒");
return "单条插入完成";
}
}
3.测试
我们调用未执行批处理的接口 http://localhost:8088/batch/add/product2
,使用foreach循环插入数据,我们观察调用时间为 108秒
我们调用使用批量插入的接口 http://localhost:8088/batch/add/product
,我们观察调用的时间为 56秒
如果插入的数据表结构更复杂,表数据量更大的话,批量处理形式与普通单条插入区别将更大,推荐使用批量处理模式。
项目源码地址