mongock

Error 'writeConcern is not allowed within a multi-statement transaction' when starting springboot application with mongock changeunits


I have a springboot application where am integrating mongock to add indices to the database. When I start the application, I see the below exception:

Caused by: io.mongock.api.exception.MongockException: io.mongock.api.exception.MongockException: Error in method[AddIndexToCoaching.addPartialIndexToCoaching] : Command failed with error 72 (InvalidOptions): 'writeConcern is not allowed within a multi-statement transaction' on server my-mongo-clus-shard-00-02.s5go0.mongodb.net:27017. The full response is {"ok": 0.0, "errmsg": "writeConcern is not allowed within a multi-statement transaction", "code": 72, "codeName": "InvalidOptions", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1724994211, "i": 24}}, "signature": {"hash": {"$binary": {"base64": "39UxbjF63gosdF8ktyThibybXBQ=", "subType": "00"}}, "keyId": 7357981548798279686}}, "operationTime": {"$timestamp": {"t": 1724994211, "i": 24}}}
    at io.mongock.driver.mongodb.springdata.v4.SpringDataMongoV4DriverBase.executeInTransaction(SpringDataMongoV4DriverBase.java:99)
    at io.mongock.runner.core.executor.ChangeExecutorBase.processChangeLogInTransactionIfApplies(ChangeExecutorBase.java:147)
    at io.mongock.runner.core.executor.ChangeExecutorBase.processSingleChangeLog(ChangeExecutorBase.java:119)
The mongo URL being used in the mongo template is mongodb+srv://user:<password>@my-mongo-clus.s5go0.mongodb.net/dbname?retryWrites=true&w=majority

The below is the change unit am trying to add:

@ChangeUnit(id="addPartialIndexToCoaching", order = "0001", author = "user1")
@Slf4j
public class AddIndexToCoaching {

  @Execution
  public void addPartialIndexToCoaching(MongoTemplate mongoTemplate) {
    // Create the index fields and sorting
    Document indexFields = new Document()
        .append("field1", 1)
        .append("field2", 1)
        .append("field3", 1);

    // Define the partial filter expression using Criteria
    Document partialFilterExpression = Criteria.where("status").is("DRAFT").getCriteriaObject();

    // Create the IndexOptions with the partial filter expression
    IndexOptions options = new IndexOptions().partialFilterExpression(partialFilterExpression);

    // Create the index using MongoTemplate
    mongoTemplate.getCollection("mycollection").createIndex(indexFields, options);
  }

  @RollbackExecution
  @RollbackBeforeExecution
  public void rollback() {
  }
}

My main app has the below enabled:

@EnableKafka
@EnableMongoRepositories(basePackages = {
    "my.backend.da.repository"})
@EnableMongoAuditing
@EnableAuthorization
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
@EnableCaching
@EnableRetry
@EnableMongock
@EnableSchedulerLock(defaultLockAtMostFor = "${shedlock.defaultLockAtMostFor}")
@EnableTransactionManagement
@EnableCronMonitoring
public class App {

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }
}

Am trying to start the main springboot app with changeunit added to the package and was expecting that the change unit gets applied to the collection. Am not sure what is wrong here. Any help is appreciated.


Solution

  • This is happening because by default Mongock wraps the changeUnit within a transaction. However, MongoDB doesn't allow DDL operations within transactions.

    To fix it, you can mark your changeUnit with transactional=false, as follow:

    @ChangeUnit(id="addPartialIndexToCoaching", order = "0001", author = "user1", transactional = false)
    @Slf4j
    public class AddIndexToCoaching {
    
      @Execution
      public void addPartialIndexToCoaching(MongoTemplate mongoTemplate) {
        // Create the index fields and sorting
        Document indexFields = new Document()
            .append("field1", 1)
            .append("field2", 1)
            .append("field3", 1);
    
        // Define the partial filter expression using Criteria
        Document partialFilterExpression = Criteria.where("status").is("DRAFT").getCriteriaObject();
    
        // Create the IndexOptions with the partial filter expression
        IndexOptions options = new IndexOptions().partialFilterExpression(partialFilterExpression);
    
        // Create the index using MongoTemplate
        mongoTemplate.getCollection("mycollection").createIndex(indexFields, options);
      }
    
      @RollbackExecution
      @RollbackBeforeExecution
      public void rollback() {
      }
    }