在实际的项目开发过程中,常常遇到批量保存数据的场景,当数据量比较少,比如只有几条数据的情况下,我们可以使用 for 循环来 insert 数据,但如果数据量比较多的情况下就不行,特别是并发的情况下,因为这样会增加数据库的负担。
我们通过查看 mybatis-plus 源码发现,mybatis-plus 的 IService API 接口提供了批量插入的接口:
public interface IService<T> {
......
/**
* 插入(批量)
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
return saveBatch(entityList, DEFAULT_BATCH_SIZE);
}
......
查看该批量插入的实现方法源码:
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
try (SqlSession batchSqlSession = sqlSessionBatch()) {
int i = 0;
// 在for循环中循环调用insert
for (T anEntityList : entityList) {
batchSqlSession.insert(sqlStatement, anEntityList);
if (i >= 1 && i % batchSize == 0) {
batchSqlSession.flushStatements();
}
i++;
}
batchSqlSession.flushStatements();
}
return true;
}
从源码可以看到,所谓的批量插入就是一个 for 循环插入,很明显,这不是我们想要结果。而当我们继续阅读 mybatis-plus 的源码可以发现,在 com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn
包中已经为我们实现了真正意义上的批量插入方法,这里就不贴实现的源码了,有兴趣的可以去看看。
因此,我们需要做的就是生效该批量了插入方法,从而可以让我们通过 Mapper
来调用它。
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-extensionartifactId>
<version>3.4.0version>
dependency>
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import java.util.List;
/**
* @Description: 支持自定义SQL注入方法
* @author 王廷云
* @date 2021/9/8 16:58
*/
public class CustomSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
// 获取父类SQL注入方法列表
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
// 将批量插入方法添加进去
methodList.add(new InsertBatchSomeColumn());
return methodList;
}
}
在这里,我们先获取默认的 SQL 注入方法列表,然后再讲批量插入方法添加进去。默认的方法列表有哪些呢,下面是我通过断点调试的截图:
可以看到,默认就是我们常用的 Insert
、Delete
、Update
、SelectOne
等方法。
@Configuration
public class MybatisPlusConfig {
@Bean
public CustomSqlInjector customSqlInjector() {
return new CustomSqlInjector();
}
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.Collection;
/**
* 自定义Mapper,添加批量插入接口
*/
public interface CustomMapper<T> extends BaseMapper<T> {
/**
* 批量插入
* @param entityList 实体列表
* @return 影响行数
*/
Integer insertBatchSomeColumn(Collection<T> entityList);
}
注意: 不要忘记在启动类 Application 中添加 @MapperScan(basePackages = "xxx.xxx")
使该 Mapper 被扫描到。
1)在数据库中创建用户表
CREATE TABLE `user`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '姓名',
`age` INT(4) NOT NULL DEFAULT 0 COMMENT '年龄',
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
2)编写实体类
@Data
@Accessors(chain = true)
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value = "name")
private String name;
@TableField(value = "age")
private Integer age;
}
3)编写 Mapper 接口
public interface UserMapper extends CustomMapper<User> {
}
4)编写 DAO 层
@Repository
public class UserDao {
@Autowired
private UserMapper userMapper;
public void insertBatch(List<User> userList) {
userMapper.insertBatchSomeColumn(userList);
}
}
5)编写 Service 层
@Service
public class UserService {
@Autowired
private UserDao userDao;
/**
* 批量插入用户
*/
public void insertUserBatch() {
List<User> list = new ArrayList<>();
list.add(new User().setName("Andy").setAge(18));
list.add(new User().setName("wang").setAge(20));
userDao.insertBatch(list);
}
}
6)测试
当调用 UserService 的 insertUserBatch() 方法时,就会触发批量插入,查看 SQL 输出日志:
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2458318b] will not be managed by Spring
==> Preparing: INSERT INTO user (name,age) VALUES (?,?) , (?,?)
==> Parameters: Andy(String), 18(Integer), wang(String), 20(Integer)
<== Updates: 2
可以看到,批量插入的 SQL 是真正意义上的批量插入。
注意: 查看 Mybatis-plus 的 SQL 输出日志需要添加配置:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl