You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}}}

问题定位

报错的核心是事务内部触发未捕获异常导致事务被中止,具体触发点:

  1. runAlways = true 引发重复执行冲突:迁移类设置runAlways = true后,每次启动都会执行execution方法。但Questionnaire的type字段是唯一索引,第二次执行时保存同type=A的文档会触发唯一键约束异常,MongoDB会自动中止当前事务,后续操作找不到事务就抛出NoSuchTransaction错误。
  2. 无冲突处理的保存逻辑:如果QuestionnaireService.save()直接调用MongoTemplate的save()而非更新逻辑,文档已存在时会抛出DuplicateKeyException,未捕获的异常直接导致事务失败。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:25:02