Mybatis-Plus批量插入用法詳解
mybatis-plus的IService接口默認(rèn)提供saveBatch批量插入,也是唯一一個默認(rèn)批量插入,在數(shù)據(jù)量不是很大的情況下可以直接使用,但這種是一條一條執(zhí)行的效率上會有一定的瓶頸,今天我們就來研究研究mybatis-plus中的批量插入。
1. 準(zhǔn)備測試環(huán)境
新建一個測試表,用插入5000條數(shù)據(jù)來測試
CREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `user_name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, `account` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=60204 DEFAULT CHARSET=utf8;
pom依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
</dependencies>
yml配置
spring:
application:
name: example-server
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/my_user?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
main:
allow-circular-references: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #開啟SQL語句打印實體類
/**
* @description: 默認(rèn)駝峰轉(zhuǎn)換
* @author: yh
* @date: 2022/8/29
*/
@Data
public class Users extends Model<Users> {
/**
* id自增
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private LocalDateTime createTime;
private String userName;
private Integer age;
private String email;
private String address;
private String account;
private String password;
}UsersMapper
public interface UsersMapper extends BaseMapper<Users> {
}UsersMapper.xml
<?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.mapper.UsersMapper"> </mapper>
service接口
public interface UsersService extends IService<Users> {
}service實現(xiàn)
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
}配置掃描
@MapperScan("com.example.mapper")
@SpringBootApplication
public class SpringExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringExampleApplication.class, args);
}
}環(huán)境準(zhǔn)備完成。
2. saveBatch
@RequestMapping(value = "/api")
@RestController
@Slf4j
public class ExampleController {
@Autowired
private UsersService usersService;
@RequestMapping(value = "/load", method = RequestMethod.GET)
public void load() {
List<Users> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
Users users = new Users();
users.setUserName("yy" + i);
users.setAge(18);
users.setEmail("123@qq.com");
users.setAddress("臨汾" + i);
users.setAccount("account" + i);
users.setPassword("password" + i);
list.add(users);
}
long start = System.currentTimeMillis();
usersService.saveBatch(list);
long end = System.currentTimeMillis();
System.out.println("5000條數(shù)據(jù)插入,耗時:" + (end - start));
}
}執(zhí)行過程:



5000條數(shù)據(jù)被分成了5次執(zhí)行,每次1000條,整體是一個事務(wù)


耗時:48.5秒
2.1 分析
點進saveBatch方法,看看內(nèi)部是怎么實現(xiàn)的

注意看,方法有一個事務(wù)注解,說明插入整批數(shù)據(jù)會作為一個事務(wù)進行

默認(rèn)1000條一次,怪不得執(zhí)行時每隔1000條會處于一個準(zhǔn)備執(zhí)行狀態(tài),等待幾秒后才會往下執(zhí)行(這里等待的時間就是1000個單條的insert語句執(zhí)行的時間)
再往下點是一個saveBatch接口,參數(shù)分別是插入的對象集合、插入批次數(shù)量也就是默認(rèn)的1000

查看它的實現(xiàn)類

這里也有一個事務(wù)的注解,這是因為saveBatch是一個重載方法,插入的時候也可以指定插入批次數(shù)量調(diào)用

繼續(xù)往下進入executeBatch

再往下

這里就是真正執(zhí)行的方法了,idxLimit會對比DEFAULT_BATCH_SIZE和集合長度兩個數(shù)中的最小數(shù),作為批量大小,也就是說當(dāng)集合長度不夠1000,那么執(zhí)行的時候批量大小就是集合的長度,就執(zhí)行一次。
for循環(huán)中的consumer:對應(yīng)的類型是一個函數(shù)式接口,代表一個接受兩個輸入?yún)?shù)且不返回任何內(nèi)容的操作符。意思是給定兩個參數(shù)sqlSession、循環(huán)中當(dāng)前element對象,執(zhí)行一次傳遞過來的consumer匿名函數(shù)。也就是上邊源碼里的插入匿名函數(shù)。

當(dāng)i == indLimit時:執(zhí)行一次預(yù)插入,并重新計算idxLimit的值
if中idxLimit計算規(guī)則:當(dāng)前idxLimit加batchSize(默認(rèn)1000) 和 集合長度 取最小值,計算出來的結(jié)果肯定不會超過集合的長度,最后的批次時idxLimit等于集合的長度,將這個值作為下一次執(zhí)行預(yù)插入的時間點。
sqlSession.flushStatements():當(dāng)有處于事務(wù)中的時候,起到一種預(yù)插入的作用,執(zhí)行了這行代碼之后,要插入的數(shù)據(jù)會鎖定數(shù)據(jù)庫的一行記錄,并把數(shù)據(jù)庫默認(rèn)返回的主鍵賦值給插入的對象,這樣就可以把該對象的主鍵賦值給其他需要的對象中去了,這里不是事務(wù)提交啊。
最后方法執(zhí)行完后@Transactional注解會默認(rèn)提交事務(wù),如果調(diào)用的方法上還有@Transactional注解,默認(rèn)的事務(wù)傳播類型是Propagation.REQUIRED,不會新開啟事務(wù),如果沒有@Transactional注解才會新開起事務(wù)
解析spring事務(wù)管理@Transactional為什么要添加rollbackFor=Exception.class
3. insert循環(huán)插入
把數(shù)據(jù)庫中的數(shù)據(jù)清空,還原到空表狀態(tài)
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
@Override
public void insertList() {
List<Users> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
Users users = new Users();
users.setUserName("yy" + i);
users.setAge(18);
users.setEmail("123@qq.com");
users.setAddress("臨汾" + i);
users.setAccount("1" + i);
users.setPassword("pw" + i);
list.add(users);
}
long start = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
baseMapper.insert(list.get(i));
}
long end = System.currentTimeMillis();
System.out.println("5000條數(shù)據(jù)插入,耗時:" + (end - start));
}
}
5000條數(shù)據(jù)每條都是一個單獨的事務(wù)。就是一條條插入,成功失敗都不會互相影響
耗時:161.2秒
4. 自定義sql插入
UsersMapper.xml
<insert id="insertList">
insert into users(user_name, age, email, address, account, password) values
<foreach collection="list" item="it" separator=",">
(#{it.userName}, #{it.age}, #{it.email}, #{it.address}, #{it.account}, #{it.password})
</foreach>
</insert>UsersMapper
void insertList(@Param("list") List<Users> list);@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
@Override
public void insertList() {
List<Users> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
Users users = new Users();
users.setUserName("yy" + i);
users.setAge(18);
users.setEmail("123@qq.com");
users.setAddress("臨汾" + i);
users.setAccount("1" + i);
users.setPassword("pw" + i);
list.add(users);
}
long start = System.currentTimeMillis();
baseMapper.insertList(list);
long end = System.currentTimeMillis();
System.out.println("5000條數(shù)據(jù)插入,耗時:" + (end - start));
}
}
這里是把要插入的數(shù)據(jù)拼接在一個insert語句后面執(zhí)行
INSERT INTO users (user_name,age,email,address,account,password) VALUES (?,?,?,?,?,?) , (?,?,?,?,?,?) , (?,?,?,?,?,?).....
這種效率是最高的,但是這種需要我們在每個批量插入對應(yīng)的xml中取寫sql語句,有點不太符合現(xiàn)在提倡的免sql開發(fā),下面介紹一下它的升級版
5. insertBatchSomeColumn
mybatis-plus提供了InsertBatchSomeColumn批量insert方法。通過SQL 自動注入器接口 ISqlInjector注入通用方法 SQL 語句 然后繼承 BaseMapper 添加自定義方法,全局配置 sqlInjector 注入 MP 會自動將類所有方法注入到 mybatis 容器中。我們需要通過這種方式注入下。
MySqlInjector.java
/**
* 自定義Sql注入
* @author: yh
* @date: 2022/8/30
*/
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
//增加自定義方法,字段注解上不等于FieldFill.DEFAULT的字段才會插入
methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.DEFAULT));
return methodList;
}
}MybatisPlusConfig.java
@Configuration
public class MybatisPlusConfig {
@Bean
public MySqlInjector sqlInjector() {
return new MySqlInjector();
}
}自定義MyBaseMapper
public interface MyBaseMapper <T> extends BaseMapper<T> {
int insertBatchSomeColumn(List<T> entityList);
}mapper繼承的這里也改下
public interface UsersMapper extends MyBaseMapper<Users> {
}插入的時候過濾字段,需要配置屬性
@Data
public class Users extends Model<Users> {
/**
* id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
// 插入的時候過濾這個字段,默認(rèn)值就是FieldFill.DEFAULT
@TableField(fill = FieldFill.DEFAULT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT)
private String userName;
@TableField(fill = FieldFill.INSERT)
private Integer age;
@TableField(fill = FieldFill.INSERT)
private String email;
@TableField(fill = FieldFill.INSERT)
private String address;
@TableField(fill = FieldFill.INSERT)
private String account;
@TableField(fill = FieldFill.INSERT)
private String password;
}修改一下service調(diào)用
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Override
public void insertList() {
List<Users> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
Users users = new Users();
users.setUserName("yy" + i);
users.setAge(18);
users.setEmail("123@qq.com");
users.setAddress("臨汾" + i);
users.setAccount("1" + i);
users.setPassword("pw" + i);
list.add(users);
}
long start = System.currentTimeMillis();
baseMapper.insertBatchSomeColumn(list);
long end = System.currentTimeMillis();
System.out.println("5000條數(shù)據(jù)插入,耗時:" + (end - start));
}
}運行結(jié)果:

createTime字段被過濾掉了


執(zhí)行時間和上邊自定義sql一樣,在1秒內(nèi)浮動,如果量太大幾十萬條建議多線程分批處理。
參考文章
Java實現(xiàn)多線程大批量同步數(shù)據(jù)(分頁)
到此這篇關(guān)于Mybatis-Plus批量插入應(yīng)該怎么用的文章就介紹到這了,更多相關(guān)Mybatis-Plus批量插入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springmvc級聯(lián)屬性處理無法轉(zhuǎn)換異常問題解決
這篇文章主要介紹了springmvc級聯(lián)屬性處理無法轉(zhuǎn)換異常問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12
通過Java實現(xiàn)反向代理集群服務(wù)的平滑分配
這篇文章主要介紹了如何通過Java語言,自己編寫的平滑加權(quán)輪詢算法,結(jié)合線程池和Socket?網(wǎng)絡(luò)編程等,并實現(xiàn)反向代理集群服務(wù)的平滑分配,需要的可以參考一下2022-04-04
詳解SpringCloud LoadBalancer 新一代負(fù)載均衡器
這篇文章主要為大家介紹了SpringCloud LoadBalancer新一代負(fù)載均衡器詳解使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
IntelliJ IDEA報錯Error:java: Compilation failed: internal java
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA報錯Error:java: Compilation failed: internal java compiler error的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10

