SpringBoot MongoDB 색인 충돌 분석 및 해결 방법

배경
spring-data-mongo 는 MongoDB 기반 ORM-Mapping 능력 을 실현 하 였 습 니 다.
간단 한 주석,Query 패 키 징 과 도구 류 를 통 해 대상 작업 을 통 해 집합,문서 의 삭제 와 검 사 를 실현 할 수 있 습 니 다.
SpringBoot 시스템 에서 spring-data-mongo 는 MongoDB 자바 도구 창고 의 두 가지 선택 입 니 다.
2.문제 발생
프로젝트 문제 의 추적 에서 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.(MongoPersistentEntityIndexCreator.java:91)
at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.(MongoPersistentEntityIndexCreator.java:68)
at org.springframework.data.mongodb.core.MongoTemplate.(MongoTemplate.java:229)
at org.bootfoo.BootConfiguration.mongoTemplate(BootConfiguration.java:121)
at org.bootfoo.BootConfiguration$$EnhancerBySpringCGLIB$$1963a75.CGLIB$mongoTemplate$2()
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)
핵심 정보:org.springframework.dao.Data Integrity ViolationException:색인 을 만 들 수 없습니다
이상 정보 에서 볼 때 색인 충돌(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 필드 에 색인 이 정의 되 어 있 습 니 다.유 니 크=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 라 는 색인 이 존재 하지만 유일한 색인 은 아 닙 니 다!
3.상세 분석
오류 가 발생 한 원인 을 확인 하기 위해 서,우 리 는 Mongo Shell 을 통 해 색인 생 성 을 실행 하려 고 시도 하 였 는데,같은 오 류 를 되 돌려 주 었 음 을 발견 하 였 다.
데이터베이스 에 있 는 색인 을 삭제 하거나 유 니 크=true 로 수정 하면 현재 문 제 를 해결 할 수 있 습 니 다.
엄밀 한 측면 에서 볼 때 색인 충돌 로 인해 SpringBoot 서비스 가 시작 되 지 않 고 받 아들 일 수 있 습 니 다.
그러나 유연성 으로 볼 때 색인 자동 생 성 을 사용 하지 않 거나 로그 만 인쇄 할 수 있 는 방법 이 있 습 니까?
Google spring data mongodb 에서 인덱스 생 성 을 비활성화 하려 고 시도 합 니 다.
발견JIRA-DATAMONGO-12012015 년 에 이미 제기 되 었 고 지금까지 해결 되 지 않 았 다.

그림.
stackoverflow 에서 많은 것 을 찾 았 습 니 다같은 문제,
그러나 대부분의 해답 은 색인 주 해 를 사용 하지 않 고 다른 방식 으로 색인 을 관리 하 는 것 이다.
이 결과 들 은 결코 만 족 스 럽 지 못 하 다.
spring-data-mongo 의 메커니즘 을 보고 Mongo PersistentEntity IndexCreator 클래스 로 찾 아 보십시오.
초기 화 방법 에 서 는 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 를 가 져 옵 니 다.
그 다음 에 일련의 초기 화 를 완 성 했 습 니 다.전체 과정 은 다음 과 같 습 니 다.
  • 몽골 템 플 릿 을 예화 합 니 다
  • ..............................................................
  • 몽골 PersistentEntityIndexCreator 를 예화 합 니 다
  • 색인 초기 화(MappingContext 를 통 해 실체 가 있 음)
  • Repository 초기 화->MappingContext 발표 맵 이벤트;
  • applicationContext 는 이 벤트 를 IndexCreator 에 알 립 니 다
  • IndexCreator 색인 만 들 기실례 화 과정 에서 색인 생 성 을 막 을 수 있 는 설정 이 없습니다.
    4.문제 해결
    앞의 분석 에서 문 제 는 IndexCreator 에 있 습 니 다.사용자 정의 실현 을 제공 할 수 있 습 니까?답 은 가능 합 니 다!
    실현 의 요점 은 다음 과 같다.
  • 하나의 IndexCreator 를 실현 하면 MongoPersistentEntityIndexCreator 를 계승 하여 색인 생 성 기능 을 제거 할 수 있 습 니 다
  • MongoConverter 와 MongoTemplate 를 예화 할 때 빈 MongoMappingContext 대상 을 사용 하여 색인 을 초기 화 하지 않도록 합 니 다
  • 사용자 정의 IndexCreator 를 Bean 으로 등록 합 니 다.이렇게 prepareIndexCreator 방법 이 실 행 될 때 원래 의 MongoPersistent Entity IndexCreator 는 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 는 Mongo Persistent Entity IndexCreator 를 계승 하여 Mapping ContextEvent 사건 의 감청 을 자동 으로 인수 합 니 다.
    업무 수행 에 있어 서 필요 에 따라 색인 처 리 를 완성 할 수 있 습 니 다!
    작은 매듭
    spring-data-mongo 는 매우 큰 편의 성 을 제공 하지만 유연성 지원 에 있어 서 는 여전히 부족 하 다.상술 한 방법 은 사실상 약간 모호 해서 공식 문서 에서 이런 방식 을 언급 하지 않 았 다.
    ORM-Mapping 프레임 워 크 는 Schema 맵 처 리 를 실현 할 때 검증 단 계 를 고려 해 야 합 니 다.예 를 들 어 Hibernate 는 none/create/update/vaidation 여러 가지 선택 을 제공 합 니 다.이것 은 개발 자 에 게 더욱 우호 적 이기 때 문 입 니 다.
    spring-data-mongo 가 후속 발전 에서 Schema 의 관리 기능 을 조속히 보완 할 수 있 기 를 기대 합 니 다!
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기