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自动索引创建前完成迁移操作,同时通过迁移历史记录保证幂等性。
实现步骤
- 创建迁移监听器类:
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())); } } } }
- 在启动类中注册监听器:
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执行迁移,避免实体加载时触发索引冲突。
实现步骤
- 在
application.yml中关闭自动索引:
spring: data: mongodb: auto-index-creation: false
- 编写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)); } }
- 配置Mongock读取
application.yml中的MongoDB参数,确保Mongock在Spring Data初始化前完成迁移。
关键注意事项
- 索引名称:默认索引名为
字段名_1,若之前自定义过索引名称,需通过MongoDB客户端查询集合索引列表确认后替换。 - 多环境适配:两种方案均直接读取
application.yml配置,不同环境的MongoDB参数会自动生效。 - 幂等性:方案1通过迁移历史集合记录执行状态,方案2依赖Mongock内置的版本控制机制,都能保证迁移仅执行一次。
内容的提问来源于stack exchange,提问作者anz
相关产品推荐
相关产品推荐

