您好,登錄后才能下訂單哦!
小編給大家分享一下SpringBoot MongoDB索引沖突怎么辦,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
一、背景
spring-data-mongo 實現了基于 MongoDB 的 ORM-Mapping 能力,
通過一些簡單的注解、Query封裝以及工具類,就可以通過對象操作來實現集合、文檔的增刪改查;
在 SpringBoot 體系中,spring-data-mongo 是 MongoDB Java 工具庫的不二之選。
二、問題產生
在一次項目問題的追蹤中,發現SpringBoot 應用啟動失敗,報錯信息如下:
Error creating bean with name 'mongoTemplate' defined in class path resource [org/bootfoo/BootConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.mongodb.core.MongoTemplate]: Factory method 'mongoTemplate' threw exception; nested exception is org.springframework.dao.DataIntegrityViolationException: Cannot create index for 'deviceId' in collection 'T_MDevice' with keys '{ "deviceId" : 1}' and options '{ "name" : "deviceId"}'. Index already defined as '{ "v" : 1 , "unique" : true , "key" : { "deviceId" : 1} , "name" : "deviceId" , "ns" : "appdb.T_MDevice"}'.; nested exception is com.mongodb.MongoCommandException: Command failed with error 85: 'exception: Index with name: deviceId already exists with different options' on server 127.0.0.1:27017. The full response is { "createdCollectionAutomatically" : false, "numIndexesBefore" : 6, "errmsg" : "exception: Index with name: deviceId already exists with different options", "code" : 85, "ok" : 0.0 }
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)...
Caused by: org.springframework.dao.DataIntegrityViolationException: Cannot create index for 'deviceId' in collection 'T_MDevice' with keys '{ "deviceId" : 1}' and options '{ "name" : "deviceId"}'. Index already defined as '{ "v" : 1 , "unique" : true , "key" : { "deviceId" : 1} , "name" : "deviceId" , "ns" : "appdb.T_MDevice"}'.; nested exception is com.mongodb.MongoCommandException: Command failed with error 85: 'exception: Index with name: deviceId already exists with different options' on server 127.0.0.1:27017. The full response is { "createdCollectionAutomatically" : false, "numIndexesBefore" : 6, "errmsg" : "exception: Index with name: deviceId already exists with different options", "code" : 85, "ok" : 0.0 }
at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.createIndex(MongoPersistentEntityIndexCreator.java:157)
at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.checkForAndCreateIndexes(MongoPersistentEntityIndexCreator.java:133)
at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.checkForIndexes(MongoPersistentEntityIndexCreator.java:125)
at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.<init>(MongoPersistentEntityIndexCreator.java:91)
at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.<init>(MongoPersistentEntityIndexCreator.java:68)
at org.springframework.data.mongodb.core.MongoTemplate.<init>(MongoTemplate.java:229)
at org.bootfoo.BootConfiguration.mongoTemplate(BootConfiguration.java:121)
at org.bootfoo.BootConfiguration$$EnhancerBySpringCGLIB$$1963a75.CGLIB$mongoTemplate$2(<generated>)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 58 moreCaused by: com.mongodb.MongoCommandException: Command failed with error 85: 'exception: Index with name: deviceId already exists with different options' on server 127.0.0.1:27017. The full response is { "createdCollectionAutomatically" : false, "numIndexesBefore" : 6, "errmsg" : "exception: Index with name: deviceId already exists with different options", "code" : 85, "ok" : 0.0 }
at com.mongodb.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:115)
at com.mongodb.connection.CommandProtocol.execute(CommandProtocol.java:114)
at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:168)
關鍵信息: org.springframework.dao.DataIntegrityViolationException: Cannot create index
從異常信息上看,出現的是索引沖突( Command failed with error 85 ),spring-data-mongo 組件在程序啟動時會實現根據注解創建索引的功能。
查看業務實體定義:
@Document(collection = "T_MDevice") public class MDevice { @Id private String id; @Indexed(unique=true) private String deviceId;
deviceId 這個字段上定義了一個索引, unique=true 表示這是一個唯一索引。
我們繼續 查看 MongoDB中表的定義:
db.getCollection('T_MDevice').getIndexes() >> [ { "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "appdb.T_MDevice" }, { "v" : 1, "key" : { "deviceId" : 1 }, "name" : "deviceId", "ns" : "appdb.T_MDevice" } ]
發現數據庫表中同樣存在一個名為 deviceId的索引,但是并非唯一索引!
三、詳細分析
為了核實錯誤產生的原因,我們嘗試通過 Mongo Shell去執行索引的創建,發現返回了同樣的錯誤。
通過將數據庫中的索引刪除,或更正為 unique=true 之后可以解決當前的問題。
從嚴謹度上看,一個索引沖突導致 SpringBoot 服務啟動不了,是可以接受的。
但從靈活性來看,是否有某些方式能 禁用索引的自動創建 ,或者僅僅是打印日志呢?
嘗試 google spring data mongodb disable index creation
發現 JIRA-DATAMONGO-1201 在2015年就已經提出,至今未解決。
圖
stackoverflow 找到許多 同樣問題 ,
但大多數的解答是不采用索引注解,選擇其他方式對索引進行管理。
這些結果并不能令人滿意。
嘗試查看 spring-data-mongo 的機制,定位到 MongoPersistentEntityIndexCreator 類:
初始化方法中,會根據 MappingContext(實體映射上下文)中已有的實體去創建索引
public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory, IndexResolver indexResolver) { ... //根據已有實體創建 for (MongoPersistentEntity<?> entity : mappingContext.getPersistentEntities()) { checkForIndexes(entity); } }
在接收到MappingContextEvent時,創建對應實體的索引
public void onApplicationEvent(MappingContextEvent<?, ?> event) { if (!event.wasEmittedBy(mappingContext)) { return; } PersistentEntity<?, ?> entity = event.getPersistentEntity(); // Double check type as Spring infrastructure does not consider nested generics if (entity instanceof MongoPersistentEntity) { //創建單個實體索引 checkForIndexes((MongoPersistentEntity<?>) entity); } }
MongoPersistentEntityIndexCreator是通過MongoTemplate引入的,如下:
public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { Assert.notNull(mongoDbFactory); this.mongoDbFactory = mongoDbFactory; this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter; ... // We always have a mapping context in the converter, whether it's a simple one or not mappingContext = this.mongoConverter.getMappingContext(); // We create indexes based on mapping events if (null != mappingContext && mappingContext instanceof MongoMappingContext) { indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory); eventPublisher = new MongoMappingEventPublisher(indexCreator); if (mappingContext instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); } } } ... //MongoTemplate實現了 ApplicationContextAware,當ApplicationContext被實例化時被感知 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { prepareIndexCreator(applicationContext); eventPublisher = applicationContext; if (mappingContext instanceof ApplicationEventPublisherAware) { //MappingContext作為事件來源,向ApplicationContext發布 ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); } resourceLoader = applicationContext; } ... //注入事件監聽 private void prepareIndexCreator(ApplicationContext context) { String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class); for (String creator : indexCreators) { MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class); if (creatorBean.isIndexCreatorFor(mappingContext)) { return; } } if (context instanceof ConfigurableApplicationContext) { //使 IndexCreator 監聽 ApplicationContext的事件 ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator); } }
由此可見, MongoTemplate 在初始化時,先通過 MongoConverter 帶入 MongoMappingContext,
隨后完成一系列初始化,整個過程如下:
實例化 MongoTemplate;
實例化 MongoConverter;
實例化 MongoPersistentEntityIndexCreator;
初始化索引(通過MappingContext已有實體);
Repository初始化 -> MappingContext 發布映射事件;
ApplicationContext 將事件通知到 IndexCreator;
IndexCreator 創建索引
在實例化過程中,沒有任何配置可以阻止索引的創建。
四、解決問題
從前面的分析中,可以發現問題關鍵在 IndexCreator,能否提供一個自定義的實現呢,答案是可以的!
實現的要點如下
實現一個IndexCreator,可繼承MongoPersistentEntityIndexCreator,去掉索引的創建功能;
實例化 MongoConverter和 MongoTemplate時,使用一個空的 MongoMappingContext對象避免初始化索引;
將自定義的IndexCreator作為Bean進行注冊,這樣在prepareIndexCreator方法執行時,原來的 MongoPersistentEntityIndexCreator不會監聽ApplicationContext的事件
IndexCreator 實現了ApplicationContext監聽,接管 MappingEvent事件處理。
實例化Bean
@Bean public MongoMappingContext mappingContext() { return new MongoMappingContext(); } // 使用 MappingContext 實例化 MongoTemplate @Bean public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoMappingContext mappingContext) { MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mappingContext); converter.setTypeMapper(new DefaultMongoTypeMapper(null)); MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter); return mongoTemplate; }
自定義IndexCreator
// 自定義IndexCreator實現 @Component public static class CustomIndexCreator extends MongoPersistentEntityIndexCreator { // 構造器引用MappingContext public CustomIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) { super(mappingContext, mongoDbFactory); } public void onApplicationEvent(MappingContextEvent<?, ?> event) { PersistentEntity<?, ?> entity = event.getPersistentEntity(); // 獲得Mongo實體類 if (entity instanceof MongoPersistentEntity) { System.out.println("Detected MongoEntity " + entity.getName()); //可實現索引處理.. } } }
在這里 CustomIndexCreator繼承了 MongoPersistentEntityIndexCreator ,將自動接管MappingContextEvent事件的監聽。
在業務實現上可以根據需要完成索引的處理!
以上是“SpringBoot MongoDB索引沖突怎么辦”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。