Spring Boot3.0.4升级Mongock5.2.2后遇NoSuchTransaction异常求助
Mongock 5.2.2 + Spring Boot 3.0.4 事务报错:Transaction with { txnNumber: X } has been aborted
环境与配置
- Spring Boot 3.0.4
- Mongock 5.2.2(从4.3.8升级)
- MongoDB 单节点/副本集
pom.xml 核心依赖
<dependencies> <dependency> <groupId>io.mongock</groupId> <artifactId>mongock-springboot-v3</artifactId> </dependency> <dependency> <groupId>io.mongock</groupId> <artifactId>mongodb-springdata-v4-driver</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>io.mongock</groupId> <artifactId>mongock-bom</artifactId> <version>5.2.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
事务配置类
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.transaction.TransactionOptions; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; @Configuration public class DatabaseTransactionConfiguration { @Bean public MongoTransactionManager transactionManager(MongoTemplate mongoTemplate) { TransactionOptions transactionalOptions = TransactionOptions.builder() .readConcern(ReadConcern.MAJORITY) .readPreference(ReadPreference.primary()) .writeConcern(WriteConcern.MAJORITY.withJournal(true)) .build(); return new MongoTransactionManager(mongoTemplate.getMongoDatabaseFactory(), transactionalOptions); } }
迁移类代码
package org.project.configuration.database.migration; import io.mongock.api.annotations.*; import org.project.domain.Questionnaire; import org.project.service.QuestionnaireService; import org.springframework.data.mongodb.core.MongoTemplate; @ChangeUnit(id="questionnaire-initializer", order = "6", author = "dev", runAlways = true) public class CU06_Questionnaire { @BeforeExecution public void beforeExecution(MongoTemplate mongoTemplate) { if (!mongoTemplate.collectionExists(Questionnaire.class)) { mongoTemplate.createCollection(Questionnaire.class); } } @RollbackBeforeExecution public void rollbackBeforeExecution(MongoTemplate mongoTemplate) { } @Execution public void execution(QuestionnaireService questionnaireService) { Questionnaire questionnaire = questionnaireService.loadQuestionnaireData(); questionnaireService.save(questionnaire); } @RollbackExecution public void rollbackExecution(QuestionnaireService questionnaireService) { Questionnaire questionnaire = questionnaireService.loadQuestionnaireData(); questionnaireService.delete(questionnaire); } }
application.yaml 配置
spring: data: mongodb: host: ${MONGODB_HOST:localhost} port: ${MONGODB_PORT:27017} database: ${MONGODB_DATABASE:project} auto-index-creation: true profiles: active: ${SPRING_PROFILES_ACTIVE:dev} mongock: migration-scan-package: org.project.configuration.database.migration transaction-enabled: true
实体类与枚举
// Questionnaire实体 @Data @Builder @Document @NoArgsConstructor @AllArgsConstructor public class Questionnaire { @Id private String id; @Transient @JsonIgnore @Builder.Default private Class<String> idType = String.class; private String name; private String description; @Indexed(unique = true) private QuestionnaireType type; private List<String> questions; } // QuestionnaireType枚举 @RequiredArgsConstructor @Getter public enum QuestionnaireType { A(0), B(1); private final int value; }
加载的JSON数据
{ "id": "Questionnaire-001", "name": "Questionnaire 001", "description": "Questionnaire to ...", "type": "A", "questions": [ "Question-0000", "Question-0001", "Question-0002", "Question-0003", "Question-0004", "Question-0005", "Question-0006", "Question-0007", "Question-0008", "Question-0009", "Question-0010" ] }
错误日志
io.mongock.api.exception.MongockException: Error in method[CU06_OTQuestionnaire.execution] : Command failed with error 251 (NoSuchTransaction): 'Transaction with { txnNumber: 30 } has been aborted.' on server localhost:27017. The full response is {"errorLabels": ["TransientTransactionError"], "ok": 0.0, "errmsg": "Transaction with { txnNumber: 30 } has been aborted.", "code": 251, "codeName": "NoSuchTransaction", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1679076061, "i": 99}}, "signature": {"hash": {"$binary": {"base64": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "subType": "00"}}, "keyId": 0}}, "operationTime": {"$timestamp": {"t": 1679076061, "i": 99}}}
问题定位
报错的核心是事务内部触发未捕获异常导致事务被中止,具体触发点:
runAlways = true引发重复执行冲突:迁移类设置runAlways = true后,每次启动都会执行execution方法。但Questionnaire的type字段是唯一索引,第二次执行时保存同type=A的文档会触发唯一键约束异常,MongoDB会自动中止当前事务,后续操作找不到事务就抛出NoSuchTransaction错误。- 无冲突处理的保存逻辑:如果
QuestionnaireService.save()直接调用MongoTemplate的save()而非更新逻辑,文档已存在时会抛出DuplicateKeyException,未捕获的异常直接导致事务失败。 - MongoDB事务支持缺失:若使用单节点MongoDB旧版本,或未正确配置单节点事务支持,也会导致事务操作失败。
解决方案
方案1:移除runAlways = true(推荐)
Mongock迁移脚本设计为一次性执行,无特殊需求无需每次启动都执行:
@ChangeUnit(id="questionnaire-initializer", order = "6", author = "dev") public class CU06_Questionnaire { // 其余代码不变 }
方案2:修改执行逻辑避免重复插入
若必须每次启动执行,添加存在性检查,实现更新/插入逻辑:
@Execution public void execution(QuestionnaireService questionnaireService, MongoTemplate mongoTemplate) { Questionnaire questionnaire = questionnaireService.loadQuestionnaireData(); // 查询是否存在同type的文档 Questionnaire existing = mongoTemplate.findOne(Query.query(Criteria.where("type").is(questionnaire.getType())), Questionnaire.class); if (existing != null) { // 执行更新逻辑 existing.setName(questionnaire.getName()); existing.setDescription(questionnaire.getDescription()); existing.setQuestions(questionnaire.getQuestions()); questionnaireService.save(existing); } else { questionnaireService.save(questionnaire); } }
方案3:确保MongoDB支持事务
- 单节点MongoDB:确保版本4.0+,连接字符串添加
retryWrites=true&w=majority; - 生产环境优先使用副本集或分片集群,这是MongoDB事务支持的标准环境。
方案4:修复JSON数据语法错误
修正加载的JSON中id字段的引号缺失问题(如上文中的JSON示例)。
内容的提问来源于stack exchange,提问作者1Z10
相关产品推荐
相关产品推荐

