Search code examples
mongodbspring-bootspring-data-mongodbmongock

MongockException: Command failed with error 251 (NoSuchTransaction)


I am using Spring-Boot 3.0.4 and I have upgraded Mongock from 4.3.8 to 5.2.2.

pom.xml snipped

<dependencies>
    <dependency>
        <groupId>io.mongock</groupId>
        <artifactId>mongock-springboot</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>

I have updated the old @Changelog and @ChangeSet annotations as explained in the doc https://docs.mongock.io/v5/migration/index.html, and similarly for the transactions https://docs.mongock.io/v5/features/transactions/index.html

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;

@Configuration
public class DatabaseTransactionConfiguration {
    @Bean
    MongoTransactionManager transactionManager(MongoDatabaseFactory factory) {
        return new MongoTransactionManager(factory);
    }
}

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

however I'm having the following error when starting the application:

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

What am I missing that causes this exception between Mongock and the Transactios?


Update

As suggested I fixed the Mongock Runner dependency:

<dependency>
    <groupId>io.mongock</groupId>
    <artifactId>mongock-springboot-v3</artifactId>
</dependency>

and I improved the MongoTransactionManager @Bean too:

@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);
}

However, the exception remains, but only with the Questionnaire class and the corresponding @ChangeUnit. If I comment out the @ChangeUnit annotation on the CU06_Questionnaire migration class, all the other migrations work without any problem.

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.project.domain.enumeration.QuestionnaireType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.List;

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

@RequiredArgsConstructor
@Getter
public enum QuestionnaireType {
    A(0), B(1);

    private final int value;
}

And this is the corresponding JSON that I'm loading:

{
  "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"
  ]
}

Minimal Non-Working Example


Solution

  • Mongock provides two specific modules to work with springboot (v3) and springdata (v4). You need to use the springboot v3 dependency as follow:

    <dependency>
        <groupId>io.mongock</groupId>
        <artifactId>mongock-springboot-v3</artifactId>
    </dependency>
    

    This sample project is currently working with those dependencies.


    UPDATE:

    After review your Minimal Non-Working Example, the error is due to Mongock is generating a proxy instance for your injected QuestionnaireService dependency and its return value Questionnaire. In this case, although proxing the QuestionnaireService injection is highly recommended(so the lock is guaranteed), the actual returned items(Questionnaire) don't need to and actually, in this case, it's producing the error. So the suggested solution is to annotate your dependency QuestionnaireService as follow:

    @Execution
    public void execution(@NonLockGuarded(NonLockGuardedType.RETURN) QuestionnaireService questionnaireService, QuestionnaireRepository questionnaireRepository) {
        ...
    }
    

    Please refer to our official documentation about Mongock's proxy mechanism and how to handle it at the following link:

    Mongock Proxies