SpringBootMongoDB索引沖突怎么辦

小編給大家分享一下SpringBoot MongoDB索引沖突怎么辦,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

我們提供的服務(wù)有:網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站制作、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、常熟ssl等。為上千余家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的常熟網(wǎng)站制作公司

一、背景

spring-data-mongo 實(shí)現(xiàn)了基于 MongoDB 的 ORM-Mapping能力,

通過一些簡單的注解、Query封裝以及工具類,就可以通過對象操作來實(shí)現(xiàn)集合、文檔的增刪改查;

在 SpringBoot 體系中,spring-data-mongo 是 MongoDB Java 工具庫的不二之選。

二、問題產(chǎn)生

在一次項(xiàng)目問題的追蹤中,發(fā)現(xiàn)SpringBoot 應(yīng)用啟動失敗,報(bào)錯信息如下:

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 more

Caused 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)

關(guān)鍵信息: org.springframework.dao.DataIntegrityViolationException: Cannot create index

從異常信息上看,出現(xiàn)的是索引沖突( Command failed with error 85),spring-data-mongo 組件在程序啟動時(shí)會實(shí)現(xiàn)根據(jù)注解創(chuàng)建索引的功能。

查看業(yè)務(wù)實(shí)體定義:

@Document(collection = "T_MDevice")
public class MDevice {

  @Id
  private String id;

  @Indexed(unique=true)
  private String deviceId;

deviceId 這個(gè)字段上定義了一個(gè)索引, unique=true表示這是一個(gè)唯一索引。

我們繼續(xù) 查看 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"
  }
]

發(fā)現(xiàn)數(shù)據(jù)庫表中同樣存在一個(gè)名為 deviceId的索引,但是并非唯一索引!

三、詳細(xì)分析

為了核實(shí)錯誤產(chǎn)生的原因,我們嘗試通過 Mongo Shell去執(zhí)行索引的創(chuàng)建,發(fā)現(xiàn)返回了同樣的錯誤。

通過將數(shù)據(jù)庫中的索引刪除,或更正為 unique=true之后可以解決當(dāng)前的問題。

從嚴(yán)謹(jǐn)度上看,一個(gè)索引沖突導(dǎo)致 SpringBoot 服務(wù)啟動不了,是可以接受的。

但從靈活性來看,是否有某些方式能 禁用索引的自動創(chuàng)建,或者僅僅是打印日志呢?

嘗試 google spring data mongodb disable index creation

發(fā)現(xiàn) JIRA-DATAMONGO-1201 在2015年就已經(jīng)提出,至今未解決。

SpringBoot MongoDB索引沖突怎么辦

stackoverflow 找到許多 同樣問題 ,

但大多數(shù)的解答是不采用索引注解,選擇其他方式對索引進(jìn)行管理。

這些結(jié)果并不能令人滿意。

嘗試查看 spring-data-mongo 的機(jī)制,定位到 MongoPersistentEntityIndexCreator類:

初始化方法中,會根據(jù) MappingContext(實(shí)體映射上下文)中已有的實(shí)體去創(chuàng)建索引

public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory,
      IndexResolver indexResolver) {
    ...
    //根據(jù)已有實(shí)體創(chuàng)建
    for (MongoPersistentEntity<?> entity : mappingContext.getPersistentEntities()) {
      checkForIndexes(entity);
    }
  }

在接收到MappingContextEvent時(shí),創(chuàng)建對應(yīng)實(shí)體的索引

 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) {
      //創(chuàng)建單個(gè)實(shí)體索引
      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實(shí)現(xiàn)了 ApplicationContextAware,當(dāng)ApplicationContext被實(shí)例化時(shí)被感知
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

    prepareIndexCreator(applicationContext);

    eventPublisher = applicationContext;
    if (mappingContext instanceof ApplicationEventPublisherAware) {
      //MappingContext作為事件來源,向ApplicationContext發(fā)布
      ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
    }
    resourceLoader = applicationContext;
  }

  ...
  //注入事件監(jiān)聽
  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 監(jiān)聽 ApplicationContext的事件
      ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator);
    }
  }

由此可見, MongoTemplate在初始化時(shí),先通過 MongoConverter帶入 MongoMappingContext,

隨后完成一系列初始化,整個(gè)過程如下:

  • 實(shí)例化 MongoTemplate;

  • 實(shí)例化 MongoConverter;

  • 實(shí)例化 MongoPersistentEntityIndexCreator;

  • 初始化索引(通過MappingContext已有實(shí)體);

  • Repository初始化 -> MappingContext 發(fā)布映射事件;

  • ApplicationContext 將事件通知到 IndexCreator;

  • IndexCreator 創(chuàng)建索引

在實(shí)例化過程中,沒有任何配置可以阻止索引的創(chuàng)建。

四、解決問題

從前面的分析中,可以發(fā)現(xiàn)問題關(guān)鍵在 IndexCreator,能否提供一個(gè)自定義的實(shí)現(xiàn)呢,答案是可以的!

實(shí)現(xiàn)的要點(diǎn)如下

  • 實(shí)現(xiàn)一個(gè)IndexCreator,可繼承MongoPersistentEntityIndexCreator,去掉索引的創(chuàng)建功能;

  • 實(shí)例化 MongoConverter和 MongoTemplate時(shí),使用一個(gè)空的 MongoMappingContext對象避免初始化索引;

  • 將自定義的IndexCreator作為Bean進(jìn)行注冊,這樣在prepareIndexCreator方法執(zhí)行時(shí),原來的 MongoPersistentEntityIndexCreator不會監(jiān)聽ApplicationContext的事件

  • IndexCreator 實(shí)現(xiàn)了ApplicationContext監(jiān)聽,接管 MappingEvent事件處理。

實(shí)例化Bean

 @Bean
  public MongoMappingContext mappingContext() {
    return new MongoMappingContext();
  }

  // 使用 MappingContext 實(shí)例化 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實(shí)現(xiàn)
  @Component
  public static class CustomIndexCreator extends MongoPersistentEntityIndexCreator {

    // 構(gòu)造器引用MappingContext
    public CustomIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) {
      super(mappingContext, mongoDbFactory);
    }

    public void onApplicationEvent(MappingContextEvent<?, ?> event) {
      PersistentEntity<?, ?> entity = event.getPersistentEntity();

      // 獲得Mongo實(shí)體類
      if (entity instanceof MongoPersistentEntity) {
        System.out.println("Detected MongoEntity " + entity.getName());
        
        //可實(shí)現(xiàn)索引處理..
      }
    }
  }

在這里 CustomIndexCreator繼承了 MongoPersistentEntityIndexCreator,將自動接管MappingContextEvent事件的監(jiān)聽。

在業(yè)務(wù)實(shí)現(xiàn)上可以根據(jù)需要完成索引的處理!

以上是“SpringBoot MongoDB索引沖突怎么辦”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

本文標(biāo)題:SpringBootMongoDB索引沖突怎么辦
URL鏈接:http://bm7419.com/article24/pcgpje.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)ChatGPT、外貿(mào)網(wǎng)站建設(shè)網(wǎng)站收錄、建站公司網(wǎng)站改版

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名