saveBatch id重复但是执行成功了是为什么
时间: 2025-07-16 16:24:35 浏览: 7
<think>好的,我现在需要帮助用户解决关于MyBatis-Plus中saveBatch方法在存在重复ID时仍然执行成功的问题。首先,我需要仔细分析用户提供的引用内容,并结合自己的知识来理解可能的原因。
用户提到他们从MyBatis-Plus 3.4.0升级到3.5.6后,使用save和saveBatch方法时出现主键冲突,但有时候即使存在ID重复,saveBatch仍然执行成功。这可能涉及到几个方面:主键生成策略、批量插入的实现方式以及事务管理。
首先,根据引用[3],雪花ID生成算法可能存在问题,比如系统时钟回拨或者实例数量超过限制,导致生成的ID重复。不过用户的问题是在升级后出现的,可能需要检查是否在升级后配置或版本变化影响了ID生成。
其次,引用[2]提到saveBatch方法有两种实现,一种是默认的批量插入,另一种是分批次批量插入。需要了解MyBatis-Plus在批量插入时的具体行为。如果主键是由数据库自增生成的,那么批量插入时数据库会自动分配ID,不会出现重复。但如果是应用层生成的ID,比如雪花ID,那么重复的ID会导致插入冲突。
用户的问题中,saveBatch执行成功但存在重复ID,可能是因为在批量插入时,MyBatis-Plus默认使用了批量操作,但某些情况下(比如数据库支持忽略重复或替换)可能不会抛出异常,而是继续执行。或者,可能是在事务中,前面的插入失败导致事务回滚,但用户没有正确捕获异常,导致误以为执行成功。
另外,需要考虑MyBatis-Plus的版本差异。在3.4.0到3.5.6的升级中,可能saveBatch的实现方式发生了变化,比如是否默认启用了某种主键策略,或者在处理批量插入时的错误处理机制不同。
还需要检查实体类的主键注解是否正确配置,比如是否使用了@TableId(type = IdType.ASSIGN_ID)来指定雪花ID生成策略,或者是否有其他配置导致主键重复。
可能的解决方向包括:检查ID生成策略是否正确配置,确保时钟同步以避免雪花ID重复,调整批量插入的批次大小,或者在插入前手动检查ID是否存在。此外,事务的正确处理也很重要,确保在发生异常时能够回滚。
总结起来,可能的原因包括应用层生成的ID重复、批量插入时的错误处理策略、数据库配置允许忽略重复以及事务管理的问题。需要逐一排查这些可能性,结合日志和具体配置进行分析。</think>关于MyBatis-Plus的`saveBatch`方法在ID重复时仍执行成功的现象,可能涉及以下四个关键原因及解决方案:
---
### 一、主键生成策略冲突
1. **现象分析**
- 当实体类主键字段未明确指定生成策略(如`@TableId(type = IdType.ASSIGN_ID)`),MyBatis-Plus可能默认使用雪花算法生成ID
- **升级版本后可能出现策略变化**:引用[1]提到从3.4.0升级到3.5.6后主键冲突,可能与新版默认策略调整有关
2. **解决方案**
```java
@TableId(type = IdType.ASSIGN_ID) // 显式声明雪花ID生成策略
private Long id;
```
- 检查所有实体类的主键注解配置
- 确认系统时间同步(避免雪花ID时钟回拨问题)[^3]
---
### 二、批量插入执行机制
1. **批量操作特性**
- `saveBatch`默认采用`executeBatch`批量提交(非逐条插入)
- 若数据库支持`INSERT IGNORE`或`ON DUPLICATE KEY UPDATE`语法,可能自动忽略重复记录[^2]
2. **验证方法**
```sql
SHOW CREATE TABLE your_table; -- 查看表是否设置唯一索引
```
- 检查数据库表结构是否有唯一约束
- 通过数据库日志确认实际执行的SQL语句
---
### 三、事务边界控制
1. **事务回滚机制**
- 默认情况下,Spring事务遇到异常才会回滚
- **无主键冲突异常场景**:如果数据库未抛出错误(如使用`REPLACE`语句),事务可能正常提交
2. **强制回滚配置**
```java
@Transactional(rollbackFor = Exception.class)
public void batchInsert(List<Entity> list) {
//...
}
```
- 添加事务回滚策略
- 手动检查插入结果:`if(!saveBatch(list)) throw new RuntimeException();`
---
### 四、版本升级差异
1. **行为变化分析**
对比3.4.0与3.5.6版本的更新日志:
- 3.5.0+版本优化了批量插入性能
- 可能修改了主键冲突处理逻辑(从抛出异常变为部分成功)
2. **版本回退验证**
```xml
<!-- 临时回退版本测试 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
```
---
### 总结排查步骤
1. 检查实体类主键策略配置
2. 查看数据库表结构约束
3. 分析实际执行的SQL语句
4. 添加事务回滚日志
5. 对比版本更新日志
6. 测试是否启用`rewriteBatchedStatements=true`(MySQL批处理优化参数)
---
阅读全文
相关推荐

















@Override
@Transactional
public void createTemporarySpotCheckOrder(CreateTemporarySpotCheckLo createTemporarySpotCheckLo) {
MaintTypeEnum maintType = MaintTypeEnum.DRC;
MaintForm maintForm;
List<MaintFormEqp> formEqps = new ArrayList<>();
List<MaintFormAction> equipmentActions = new ArrayList<>();
List<MaintFormActionInput> cells = new ArrayList<>();
String eqpCode = createTemporarySpotCheckLo.getEqpCode();
String planEffectTime = createTemporarySpotCheckLo.getPlanEffectTime();
LocalDate planDate = null;
LocalDateTime planEffectDate = null;
if (StringUtils.isNotEmpty(planEffectTime)) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
planDate = LocalDate.parse(planEffectTime, formatter);
planEffectDate = LocalDateTime.parse(planEffectTime, formatter);
}
String isImedEffect = createTemporarySpotCheckLo.getIsImedEffect();
maintForm = newForm(null, planDate, eqpCode, new MaintPlan(), MaintTypeEnum.TRC, new MaintPlanItem(), null, isImedEffect, planEffectDate);
LambdaQueryWrapper<EquipmentCapability> qm = new LambdaQueryWrapper<>();
qm.eq(EquipmentCapability::getEqpCode, eqpCode);
qm.eq(EquipmentCapability::getActive, "Y");
EquipmentCapability equipmentCapability = equipmentCapabilityMapper.selectOne(qm);
LambdaQueryWrapper<EquipmentDef> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(EquipmentDef::getEqpCode, eqpCode);
EquipmentDef equipmentDef = equipmentDefMapper.selectOne(queryWrapper);
MaintManual manual = getManual(equipmentDef.getBrandCode(), maintType, equipmentCapability, createTemporarySpotCheckLo.getDescEncode());
if (manual == null) {
throw new BusinessException("9999", "该设备不存在已激活的维保手册!");
}
//临时点检名称使用手册全名
maintForm.setMaintPlanFullname(manual.getMaintManualFullname());
//临时点检标题使用手册描述
maintForm.setFormTitle(manual.getDescription());
//表单编号和版本
maintForm.setCode(manual.getCode());
maintForm.setVersion(manual.getVersion());
//临时点检备注提示取手册中的
maintForm.setFillinComment(manual.getNotes());
//设置表单类型
maintForm.setFormType(createTemporarySpotCheckLo.getDescEncode());
MaintFormEqp formEqp = new MaintFormEqp();
formEqp.setFormCode(maintForm.getFormCode());
formEqp.setEqpCode(equipmentDef.getEqpCode());
formEqp.setMaintManualFullname(manual.getMaintManualFullname());
formEqp.setMaintType(maintType.name());
formEqp.setSortingOrder(String.valueOf(0));
formEqp.setPeriodConditionCode("UNSCHEDULED");
formEqps.add(formEqp);
String maintManualFullname = manual.getMaintManualFullname();
if (StringUtils.isNotBlank(manual.getRefMaintManualName())) {
MaintManual refMaintManual = getMaintManualByVersionOrActive(manual.getRefMaintManualName(), manual.getRefMaintManualVersion());
if (null == refMaintManual) {
throw new BusinessException("9999", "refMaintManual不存在!");
}
maintManualFullname = refMaintManual.getMaintManualFullname();
}
// equipment action record
List<MaintManualItem> manualItems = getMaintManualItems(maintManualFullname, "UNSCHEDULED");
if (CollectionUtils.isEmpty(manualItems)) {
throw new BusinessException("9999", "该设备的激活维保手册没有维护不定期点检项目!");
}
List<BasicActionWrapper> basicActions = new ArrayList<>();
for (MaintManualItem manualItem : manualItems) {
if (manualItem.getGroupAction()) {
List<BasicActionWrapper> actions = getBasicActionsOfGroup(manualItem.getActionName(), manualItem.getActionVersion());
basicActions.addAll(actions);
} else {
BasicAction action = getBasicActionByNameVersion(manualItem.getActionName(), manualItem.getActionVersion());
BasicAction basicAction = new BasicAction();
try {
BeanUtils.copyProperties(action, basicAction);
} catch (Exception e) {
throw new BusinessException("9999", "事项" + manualItem.getActionName() + "不存在" + (manualItem.getActionVersion().equals(ModelVersion.AA) ? "激活的版本!" : manualItem.getActionVersion() + "版本!"));
}
// 取manual中的PhotoRequired
basicAction.setPhotoRequired(manualItem.getPhotoRequired());
BasicActionWrapper wrapper = new BasicActionWrapper(basicAction, null);
basicActions.add(wrapper);
}
}
calculateSortOrderForActions(basicActions); // 计算每个form action的序号
for (int j = 0; j < basicActions.size(); j++) {
BasicActionWrapper actionWrapper = basicActions.get(j);
MaintFormAction formAction = new MaintFormAction();
formAction.setFormCode(maintForm.getFormCode());
formAction.setEqpCode(formEqp.getEqpCode());
formAction.setBasicActionFullname(actionWrapper.basicAction.getBasicActionFullname());
formAction.setPhotoRequired(actionWrapper.basicAction.getPhotoRequired());
formAction.setGroupActionFullname(actionWrapper.groupActionFullname);
formAction.setSortingOrder(actionWrapper.sortingOrder);
equipmentActions.add(formAction);
// action items(cells)
for (int row = 0; row < actionWrapper.basicAction.getTotalRow(); row++) {
for (int col = 0; col < actionWrapper.basicAction.getTotalCol(); col++) {
MaintFormActionInput cell = new MaintFormActionInput();
cell.setFormCode(maintForm.getFormCode());
cell.setEqpCode(equipmentDef.getEqpCode());
cell.setBasicActionFullname(actionWrapper.basicAction.getBasicActionFullname());
cell.setRow(row);
cell.setCol(col);
cell.setSortingOrder(formAction.getSortingOrder());
cells.add(cell);
}
}
}
// save records to db
maintFormMapper.insert(maintForm);
LOGGER.info("formEqps:" + formEqps);
for (MaintFormEqp maintFormEqp : formEqps) {
maintFormEqpMapper.insert(maintFormEqp);
}
LOGGER.info("equipmentActions:" + equipmentActions);
for (MaintFormAction maintFormAction : equipmentActions) {
maintFormActionMapper.insert(maintFormAction);
}
LOGGER.info("cells:" + cells);
for (MaintFormActionInput maintFormActionInput : cells) {
maintFormActionInputMapper.insert(maintFormActionInput);
}
//推送消息
LambdaQueryWrapper<SpotCheckPushSetting> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SpotCheckPushSetting::getEqpCode, eqpCode)
.eq(SpotCheckPushSetting::getNoticeType, SpotCheckPushSettingEnum.CREATED.getCode())
.eq(SpotCheckPushSetting::getDeleteStatus, "Y");
SpotCheckPushSetting setting = spotCheckPushSettingMapper.selectOne(wrapper);
String userNumber = null;
//判断是否有维护点检通知
if (setting != null) {
//获取周期频率的中文描述使用
//查询定期的维保周期
List periods = new LambdaQueryChainWrapper<>(periodMapper).list();
//基于维保周期Code将集合转化为map
Map<String, Period> periodMap = periods.stream()
.collect(Collectors.toMap(Period::getPeriodCode, Function.identity()));
Period period = periodMap.get(maintForm.getPeriodCode());
//维保周期没有维护,但是计划里面有相应的定期动作,则提示异常并结束
if (null == period) {
LOGGER.error(String.format("The period [%s] is not existed", maintForm.getPeriodCode()));
return;
}
userNumber = setting.getNoticePersonNumber();
String descEncode = createTemporarySpotCheckLo.getDescEncode();
if (descEncode.length() >= "SBGCDJ".length() &&
descEncode.substring(0, "SBGCDJ".length()).equals("SBGCDJ")) {
// 进入这个代码块如果前几位(至少和"SBGCDJ"一样长)是"SBGCDJ"
String emailContent = "有一份设备过程点检工单创建成功!"
+ "
表单:"+ maintForm.getFormTitle()
+ "
点检工单编号:" + maintForm.getFormCode()
+ "
设备:" + maintForm.getMaintObject() +"("+ equipmentDef.getDescription()+ ")"
+ "
生成时间:" + maintForm.getCreatedTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+ "
如有疑请查看:PMS设备管理系统" + jumpIp + "/platform/#/TM20/login";
String wechatContent = "有一份设备过程点检工单创建成功!"
+ "\n表单:"+ maintForm.getFormTitle()
+ "\n点检工单编号:" + maintForm.getFormCode()
+ "\n设备:" + maintForm.getMaintObject() + "(" + equipmentDef.getDescription()+ ")"
+ "\n周期频率:" + maintForm.getPeriodCode() + "(" + period.getDescription() + ")"
+ "\n生成时间:" + maintForm.getCreatedTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+ "\n如有疑请查看:PMS设备管理系统";
LogInfo logInfo = new LogInfo();
logInfo.setFormCode(maintForm.getFormCode());
logInfo.setEqpCode(maintForm.getMaintObject());
logInfo.setNoticeType(SpotCheckPushSettingEnum.CREATED.getCode());
logInfo.setUpgradeTime(setting.getUpgradeTime());
emailWeComSendBo.sendMessage(userNumber, "设备过程点检工单创建通知", emailContent, wechatContent, true, true, logInfo);
}else {
String emailContent = "有一份工艺过程点检工单创建成功!"
+ "
表单:"+ maintForm.getFormTitle()
+ "
点检工单编号:" + maintForm.getFormCode()
+ "
设备:" + maintForm.getMaintObject() +"("+ equipmentDef.getDescription()+ ")"
+ "
生成时间:" + maintForm.getCreatedTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+ "
如有疑请查看:PMS设备管理系统" + jumpIp + "/platform/#/TM20/login";
String wechatContent = "有一份工艺过程点检工单创建成功!"
+ "\n表单:"+ maintForm.getFormTitle()
+ "\n点检工单编号:" + maintForm.getFormCode()
+ "\n设备:" + maintForm.getMaintObject() + "(" + equipmentDef.getDescription()+ ")"
+ "\n周期频率:" + maintForm.getPeriodCode() + "(" + period.getDescription() + ")"
+ "\n生成时间:" + maintForm.getCreatedTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+ "\n如有疑请查看:PMS设备管理系统";
LogInfo logInfo = new LogInfo();
logInfo.setFormCode(maintForm.getFormCode());
logInfo.setEqpCode(maintForm.getMaintObject());
logInfo.setNoticeType(SpotCheckPushSettingEnum.CREATED.getCode());
logInfo.setUpgradeTime(setting.getUpgradeTime());
emailWeComSendBo.sendMessage(userNumber, "工艺过程点检工单创建通知", emailContent, wechatContent, true, true, logInfo);
}
}
}在这个方法中maintFormMapper.insert(maintForm);可以插入成功,请给出我在 /**
* 批量保存数据到数据库
*/
@Override
@Transactional
public void batchSaveData(List<MaintForm> forms, List<MaintFormEqp> formEqps,
List<MaintFormAction> equipmentActions, List<MaintFormActionInput> cells) {
if (!forms.isEmpty()) {
if (forms.stream().anyMatch(Objects::isNull)) {
throw new IllegalArgumentException("列表中存在null元素");
}
forms.forEach(form -> LOGGER.info("Form to insert: {}", form));
for (MaintForm maintForm : forms) {
maintFormMapper.insert(maintForm);
}
// maintFormMapper.maintFormBatchInsert(forms);
LOGGER.info("Saved {} forms", forms.size());
}
if (!formEqps.isEmpty()) {
maintFormEqpMapper.formEqpsBatchInsert(formEqps);
LOGGER.info("Saved {} formEqps", formEqps.size());
}
if (!equipmentActions.isEmpty()) {
maintFormActionMapper.equipmentActionsBatchInsert(equipmentActions);
LOGGER.info("Saved {} equipmentActions", equipmentActions.size());
}
if (!cells.isEmpty()) {
maintFormActionInputMapper.cellsBatchInsert(cells);
LOGGER.info("Saved {} cells", cells.size());
}
}错误的原因


