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

Spring Boot迁移中修改MongoDB集合索引并添加唯一约束的方案

解决Spring Boot启动前自动迁移MongoDB索引冲突问题

问题背景

使用Spring Boot 2.1.4 + MongoDB,实体类MyClass的id字段原有@Indexed索引,修改为@Indexed(unique = true)后启动失败,提示存在配置不同的已定义索引。尝试Mongock但实体类加载早于迁移执行,导致启动仍失败。核心需求:

  • 启动前自动执行索引迁移(删除旧索引、创建带唯一约束的新索引)
  • 适配多环境,从application.yml读取对应MongoDB配置
  • 迁移逻辑仅执行一次

原有代码:

@Document(collection = "myclass")
public class MyClass {
  ...
  @Indexed
  private String id;
  ..
}

修改后的代码:

@Document(collection = "myclass")
public class MyClass {
  ...
  @Indexed(unique = true)
  private String id;
  ..
}

可行解决方案

方案1:利用Spring Boot事件监听在上下文初始化前执行迁移

通过监听ApplicationEnvironmentPreparedEvent事件,在Spring上下文初始化、MongoDB自动索引创建前完成迁移操作,同时通过迁移历史记录保证幂等性。

实现步骤

  1. 创建迁移监听器类:
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class MongoIndexMigrationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    private static final String MIGRATION_HISTORY_COL = "migration_history";
    private static final String MIGRATION_TASK_ID = "myclass_id_unique_index_migrate";

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        Environment env = event.getEnvironment();
        // 从配置文件读取MongoDB参数
        String mongoUri = env.getProperty("spring.data.mongodb.uri");
        String dbName = env.getProperty("spring.data.mongodb.database");

        try (var mongoClient = MongoClients.create(mongoUri)) {
            MongoDatabase db = mongoClient.getDatabase(dbName);
            MongoCollection<Document> migrationColl = db.getCollection(MIGRATION_HISTORY_COL);
            
            // 检查是否已执行过该迁移
            if (migrationColl.countDocuments(new Document("taskId", MIGRATION_TASK_ID)) == 0) {
                MongoCollection<Document> targetColl = db.getCollection("myclass");
                
                // 删除旧索引(默认索引名为字段名+_1,若自定义过需替换)
                targetColl.dropIndex("id_1");
                
                // 创建带唯一约束的新索引
                targetColl.createIndex(
                    new Document("id", 1), 
                    new com.mongodb.client.model.IndexOptions().unique(true)
                );
                
                // 记录迁移完成状态
                migrationColl.insertOne(new Document("taskId", MIGRATION_TASK_ID)
                        .append("executeTime", System.currentTimeMillis()));
            }
        }
    }
}
  1. 在启动类中注册监听器:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class YourApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(YourApplication.class);
        app.addListeners(new MongoIndexMigrationListener());
        app.run(args);
    }
}

方案2:禁用自动索引创建+Mongock迁移

先关闭Spring Data MongoDB的自动索引创建,再用Mongock执行迁移,避免实体加载时触发索引冲突。

实现步骤

  1. 在application.yml中关闭自动索引:
spring:
  data:
    mongodb:
      auto-index-creation: false
  1. 编写Mongock迁移单元:
import io.mongock.api.annotations.ChangeUnit;
import io.mongock.api.annotations.Execution;
import io.mongock.api.annotations.RollbackExecution;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

@ChangeUnit(id = "myclass-id-unique-index", order = "001", author = "dev")
public class MyClassIndexMigration {

    private final MongoDatabase mongoDatabase;

    public MyClassIndexMigration(MongoDatabase mongoDatabase) {
        this.mongoDatabase = mongoDatabase;
    }

    @Execution
    public void execute() {
        MongoCollection<Document> targetColl = mongoDatabase.getCollection("myclass");
        // 删除旧索引
        targetColl.dropIndex("id_1");
        // 创建带唯一约束的新索引
        targetColl.createIndex(
            new Document("id", 1), 
            new com.mongodb.client.model.IndexOptions().unique(true)
        );
    }

    @RollbackExecution
    public void rollback() {
        MongoCollection<Document> targetColl = mongoDatabase.getCollection("myclass");
        // 回滚操作:删除唯一索引,重建普通索引
        targetColl.dropIndex("id_1");
        targetColl.createIndex(new Document("id", 1));
    }
}
  1. 配置Mongock读取application.yml中的MongoDB参数,确保Mongock在Spring Data初始化前完成迁移。

关键注意事项

  • 索引名称:默认索引名为字段名_1,若之前自定义过索引名称,需通过MongoDB客户端查询集合索引列表确认后替换。
  • 多环境适配:两种方案均直接读取application.yml配置,不同环境的MongoDB参数会自动生效。
  • 幂等性:方案1通过迁移历史集合记录执行状态,方案2依赖Mongock内置的版本控制机制,都能保证迁移仅执行一次。

内容的提问来源于stack exchange,提问作者anz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 22:57:40