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 分析
點進(jìn)saveBatch
方法,看看內(nèi)部是怎么實現(xiàn)的
注意看,方法有一個事務(wù)注解,說明插入整批數(shù)據(jù)會作為一個事務(wù)進(jìn)行
默認(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ù)往下進(jìn)入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)文章
Java實現(xiàn)兩人五子棋游戲(四) 落子動作的實現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)兩人五子棋游戲,落子動作的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03springBoot前后端分離項目中shiro的302跳轉(zhuǎn)問題
這篇文章主要介紹了springBoot前后端分離項目中shiro的302跳轉(zhuǎn)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12maven?springboot如何將jar包打包到指定目錄
這篇文章主要介紹了maven?springboot如何將jar包打包到指定目錄,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Java、JavaScript、Oracle、MySQL中實現(xiàn)的MD5加密算法分享
這篇文章主要介紹了Java、JavaScript、Oracle、MySQL中實現(xiàn)的MD5加密算法分享,需要的朋友可以參考下2014-09-09Mybatis-Plus之ID自動增長的設(shè)置實現(xiàn)
本文主要介紹了Mybatis-Plus之ID自動增長的設(shè)置實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07