<think>
我们参考站内引用信息,特别是引用[1]和引用[3],它们都提到了使用@Insert注解进行批量插入的方法。
引用[1]中展示了一个自定义批量插入的SQL,使用<foreach>标签来遍历集合,并在values后面构造多个插入值。
引用[3]则提到了一个常见的错误,即在@Insert注解中使用集合时参数绑定问题。
因此,我们可以总结出使用@Insert注解实现批量插入的步骤:
1. 在Mapper接口中定义一个方法,参数为List集合,并使用@Param注解指定参数名(在XML或注解SQL中引用)。
2. 在@Insert注解中编写SQL语句,使用<script>标签包裹,以便使用动态SQL(如foreach)。
3. 在values后面使用foreach标签遍历集合,生成多个插入值。
注意点:
- 在注解中使用动态SQL,需要将SQL用<script>包裹,这样MyBatis才会解析其中的动态标签。
- foreach标签的collection属性需要和@Param注解指定的参数名一致。
- foreach标签中的item属性指定集合中每个元素的别名,然后在#{}中使用这个别名来访问属性。
下面是一个示例代码,假设我们有一个实体类MaterialData,对应的表是rpt_material_hour,并且我们想要批量插入一个List<MaterialData>。
但是,根据引用[1]和引用[2],我们需要注意主键生成策略。如果我们的主键是使用MyBatis-Plus的雪花算法自动生成,那么在批量插入时,我们需要确保在插入前为每个实体的主键赋值。在引用[1]中,作者提到因为自定义批处理SQL导致雪花算法失效,无法自动生成id。因此,我们需要在Java代码中手动为每个实体生成主键。
解决方法:
1. 在实体类的主键字段上使用@TableId(type = IdType.ASSIGN_ID),这样MyBatis-Plus在插入前会自动生成主键(但这是在MyBatis-Plus的Service方法中才会生效)。而如果我们使用自定义的@Insert注解,则不会触发主键生成。
2. 因此,我们需要在调用Mapper的批量插入方法之前,遍历列表,为每个实体设置主键。可以使用MyBatis-Plus的IdWorker(3.3.0之前)或者IdentifierGenerator(3.3.0之后)来生成主键。
例如,在3.3.0之后,我们可以注入IdentifierGenerator,然后使用其nextId方法生成主键。或者,我们也可以使用IdWorker.getId()(但3.4.0之后IdWorker被弃用)。
考虑到引用[2]中提到的错误,我们还要确保数据库表的主键是自增的(如果使用数据库自增策略)或者我们使用程序生成主键(如雪花算法)并设置到实体上。
因此,我们分两步:
第一步:为每个实体生成主键(如果使用雪花算法)
第二步:使用@Insert注解编写批量插入SQL
具体实现:
Mapper接口方法:
```java
@Insert("<script>" +
"INSERT INTO rpt_material_hour (id, sample_time, rounding_time, cur_month, machine_no, data_code, data_value, create_user, create_time, update_user, update_time) VALUES " +
"<foreach collection='materialDataList' item='item' separator=','>" +
" (#{item.id}, #{item.sampleTime}, #{item.roundingTime}, #{item.curMonth}, #{item.machineNo}, #{item.dataCode}, #{item.dataValue}, " +
" #{item.createUser}, #{item.createTime}, #{item.updateUser}, #{item.updateTime})" +
"</foreach>" +
"</script>")
void insertBatch(@Param("materialDataList") List<MaterialData> materialDataList);
```
在调用insertBatch方法之前,我们需要为materialDataList中的每个MaterialData对象设置id(如果主键由程序生成)。例如:
```java
// 假设我们使用MyBatis-Plus的IdentifierGenerator(需要注入)
// 或者我们可以使用IdWorker(但已弃用)或者使用配置的KeyGenerator
// 这里我们使用IdentifierGenerator(需要Spring容器)
// 在Service中
@Autowired
private IdentifierGenerator identifierGenerator;
public void batchInsert(List<MaterialData> materialDataList) {
for (MaterialData materialData : materialDataList) {
// 如果materialData的id为null,则生成id
if (materialData.getId() == null) {
// 使用IdentifierGenerator生成Long类型的ID(雪花算法)
materialData.setId(identifierGenerator.nextId(materialData));
}
// 设置其他公共字段,如createTime等
}
materialDataMapper.insertBatch(materialDataList);
}
```
注意:如果数据库表主键是自增的(AUTO_INCREMENT),那么我们应该在实体类主键字段上使用@TableId(type = IdType.AUTO),并且在插入时不需要设置主键值。但是,在批量插入的SQL中,我们通常需要将主键列排除(如果数据库自增)。然而,在MyBatis-Plus中,如果使用自定义SQL,那么自增主键的生成完全由数据库完成,我们不需要插入主键值。所以,在SQL中应该去掉id列,同时values中也不插入id值。
因此,如果主键是自增的,我们的SQL可以改为:
```java
@Insert("<script>" +
"INSERT INTO rpt_material_hour (sample_time, rounding_time, cur_month, machine_no, data_code, data_value, create_user, create_time, update_user, update_time) VALUES " +
"<foreach collection='materialDataList' item='item' separator=','>" +
" (#{item.sampleTime}, #{item.roundingTime}, #{item.curMonth}, #{item.machineNo}, #{item.dataCode}, #{item.dataValue}, " +
" #{item.createUser}, #{item.createTime}, #{item.updateUser}, #{item.updateTime})" +
"</foreach>" +
"</script>")
void insertBatch(@Param("materialDataList") List<MaterialData> materialDataList);
```
但是,在自增主键的情况下,我们可能希望获取到生成的主键值,但批量插入时MyBatis不支持返回多个主键。所以,如果不需要立即获取主键,可以这样使用。
另外,引用[3]中提到了一个错误:Parameter 'item' not found。这个错误通常是因为在foreach标签中使用了错误的参数名。在@Param("materialDataList")中我们指定了参数名为"materialDataList",然后在foreach的collection属性中使用'materialDataList'。在foreach内部,我们使用item属性指定了每个元素的别名为'item',然后在#{}中应该使用'item.属性名'。
所以,请确保在SQL中正确引用了参数。
总结:
使用@Insert注解进行批量插入,关键在于使用<script>和<foreach>动态标签构造插入多个值的SQL语句。同时,需要注意主键生成策略,在自定义SQL中,MyBatis-Plus的自动主键生成不会生效,需要手动设置主键值(如果使用程序生成主键)或者在SQL中排除主键列(如果使用数据库自增)。
下面我们给出两种情况的完整示例:
情况1:使用程序生成主键(雪花算法)
实体类:
```java
public class MaterialData {
@TableId(type = IdType.ASSIGN_ID) // 由程序分配ID(雪花算法)
private Long id;
// 其他字段...
}
```
Mapper接口:
```java
public interface MaterialDataMapper extends BaseMapper<MaterialData> {
@Insert("<script>" +
"INSERT INTO rpt_material_hour (id, sample_time, rounding_time, cur_month, machine_no, data_code, data_value, create_user, create_time, update_user, update_time) VALUES " +
"<foreach collection='materialDataList' item='item' separator=','>" +
" (#{item.id}, #{item.sampleTime}, #{item.roundingTime}, #{item.curMonth}, #{item.machineNo}, #{item.dataCode}, #{item.dataValue}, " +
" #{item.createUser}, #{item.createTime}, #{item.updateUser}, #{item.updateTime})" +
"</foreach>" +
"</script>")
void insertBatch(@Param("materialDataList") List<MaterialData> materialDataList);
}
```
Service层调用:
```java
@Service
public class MaterialDataServiceImpl implements MaterialDataService {
@Autowired
private MaterialDataMapper materialDataMapper;
@Autowired
private IdentifierGenerator identifierGenerator;
@Override
public void insertBatchMaterialData(List<MaterialData> materialDataList) {
// 为每个实体设置主键
for (MaterialData materialData : materialDataList) {
if (materialData.getId() == null) {
materialData.setId(identifierGenerator.nextId(materialData));
}
// 设置其他公共字段,如创建时间等
materialData.setCreateTime(LocalDateTime.now());
materialData.setUpdateTime(LocalDateTime.now());
}
materialDataMapper.insertBatch(materialDataList);
}
}
```
情况2:使用数据库自增主键
实体类:
```java
public class MaterialData {
@TableId(type = IdType.AUTO) // 自增主键
private Long id;
// 其他字段...
}
```
Mapper接口:
```java
public interface MaterialDataMapper extends BaseMapper<MaterialData> {
@Insert("<script>" +
"INSERT INTO rpt_material_hour (sample_time, rounding_time, cur_month, machine_no, data_code, data_value, create_user, create_time, update_user, update_time) VALUES " +
"<foreach collection='materialDataList' item='item' separator=','>" +
" (#{item.sampleTime}, #{item.roundingTime}, #{item.curMonth}, #{item.machineNo}, #{item.dataCode}, #{item.dataValue}, " +
" #{item.createUser}, #{item.createTime}, #{item.updateUser}, #{item.updateTime})" +
"</foreach>" +
"</script>")
void insertBatch(@Param("materialDataList") List<MaterialData> materialDataList);
}
```
在自增主键的情况下,我们不需要在插入前设置id,插入后MyBatis也不会回填多个主键值(批量插入不支持回填)。如果需要回填,可能需要使用其他方式(如单条插入并回填,但性能较差)。
最后,注意SQL注入问题:这里使用的是#{},是预编译的,所以是安全的。
希望以上内容能帮助您实现MyBatis-Plus使用@Insert注解的批量插入。
</think>
以下是MyBatis-Plus使用`@Insert`注解实现批量插入的完整方法和注意事项:
### 方法实现
在Mapper接口中使用`@Insert`注解结合动态SQL实现批量插入:
```java
@Insert("<script>" +
"INSERT INTO your_table (id, field1, field2) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
" (#{item.id}, #{item.field1}, #{item.field2})" +
"</foreach>" +
"</script>")
void insertBatch(@Param("list") List<YourEntity> list);
```
### 关键说明
1. **动态SQL标签**
- `<script>`:包裹整个SQL语句,启用动态SQL解析
- `<foreach>`:遍历集合,`collection`指定参数名,`item`定义元素别名
- `separator=","`:生成逗号分隔的VALUES列表
2. **主键处理(雪花ID问题)**
自定义SQL时**不会触发**MyBatis-Plus的自动ID生成,需手动赋值:
```java
// 在Service层为每个实体生成ID
list.forEach(item -> {
if(item.getId() == null) {
item.setId(IdWorker.getId()); // 雪花ID生成
}
});
mapper.insertBatch(list);
```
3. **参数绑定**
必须使用`@Param("list")`明确参数名,避免报错:
> 错误示例:`Parameter 'item' not found`[^3]
4. **字段匹配**
确保`#{}`中的属性名与实体类字段**严格匹配**(驼峰命名自动转换)
### 完整示例
```java
// Entity
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID) // 雪花ID策略
private Long id;
private String name;
private Integer age;
}
// Mapper
public interface UserMapper extends BaseMapper<User> {
@Insert("<script>" +
"INSERT INTO user (id, name, age) VALUES " +
"<foreach collection='users' item='u' separator=','>" +
" (#{u.id}, #{u.name}, #{u.age})" +
"</foreach>" +
"</script>")
void batchInsert(@Param("users") List<User> users);
}
// Service
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void saveBatch(List<User> users) {
users.forEach(u -> {
if(u.getId() == null) {
u.setId(IdWorker.getId()); // 手动生成ID
}
});
userMapper.batchInsert(users);
}
}
```
### 注意事项
1. **性能优化**
批量数据建议分批次插入(每批500-1000条),避免SQL过长
2. **主键冲突**
确保手动生成的ID在分布式系统中全局唯一
3. **事务管理**
在Service层添加`@Transactional`保证原子性
4. **字段过滤**
若数据库有默认值字段,可省略对应`#{}`表达式
> **提示**:MyBatis-Plus原生`saveBatch()`方法已优化批量插入,优先考虑使用内置方法[^1][^2]。仅当需要特殊SQL逻辑时才用此方案。