Analyze and solve pringBoot-MongoDB index conflict [share] Huawei Cloud

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/devcloud/article/details/100119386

One, background

spring-data-mongo implemented based MongoDB the ORM-Mapping capability,
some simple annotations, Query packaging and tools, can be implemented deletions set, the document by the object operation change search;
in SpringBoot system, spring-data -mongo is the best MongoDB Java tools repository of choice.

Second, problems

In a project tracking problems found SpringBoot application failed to start, the following error message:

 1 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 }
 2     at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
 3     at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
 4     at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
 5     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
 6     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
 7 
 8 ...
 9 
10 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 }
11     at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.createIndex(MongoPersistentEntityIndexCreator.java:157)
12     at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.checkForAndCreateIndexes(MongoPersistentEntityIndexCreator.java:133)
13     at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.checkForIndexes(MongoPersistentEntityIndexCreator.java:125)
14     at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.<init>(MongoPersistentEntityIndexCreator.java:91)
15     at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.<init>(MongoPersistentEntityIndexCreator.java:68)
16     at org.springframework.data.mongodb.core.MongoTemplate.<init>(MongoTemplate.java:229)
17     at org.bootfoo.BootConfiguration.mongoTemplate(BootConfiguration.java:121)
18     at org.bootfoo.BootConfiguration$$EnhancerBySpringCGLIB$$1963a75.CGLIB$mongoTemplate$2(<generated>)
19     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
20     at java.lang.reflect.Method.invoke(Unknown Source)
21     at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
22     ... 58 more
23 
24 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 }
25     at com.mongodb.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:115)
26     at com.mongodb.connection.CommandProtocol.execute(CommandProtocol.java:114)
27     at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:168)

Key information: org.springframework.dao.DataIntegrityViolationException: the Create index of Can not

Exception information from the point of view, there is an index conflict ( the Command failed The error with 85 ), the Data-mongo the Spring-components will be realized when the program starts to create an index based on function annotation.
View business entities:

1 @Document(collection = "T_MDevice")
2 public class MDevice {
3 
4     @Id
5     private String id;
6 
7     @Indexed(unique=true)
8     private String deviceId;

deviceId defines an index on this field, UNIQUE = to true indicates that this is a unique index.
We continue to view the definition of the table in MongoDB:

 1 db.getCollection('T_MDevice').getIndexes()
 2 
 3 >>
 4 [
 5     {
 6         "v" : 1,
 7         "key" : {
 8             "_id" : 1
 9         },
10         "name" : "_id_",
11         "ns" : "appdb.T_MDevice"
12     },
13     {
14         "v" : 1,
15         "key" : {
16             "deviceId" : 1
17         },
18         "name" : "deviceId",
19         "ns" : "appdb.T_MDevice"
20     }
21 ]

We found that there are also a database table called deviceId the index, but not the only index!

Third, detailed analysis

In order to verify the cause of the error generated, we try to create an index to perform by Mongo Shell, found that return the same error.
Through the indexes in the database to delete or correct unique = true can solve the current problems later.

From the point of view of rigor, an index conflict led SpringBoot service can not start, it is acceptable.
But the flexibility of view, is there some way can be created automatically disable indexing , or simply print the log it?

Try to Google the Spring disable the Data MongoDB index Creation
found JIRA-DATAMONGO-1201 in 2015, it has been proposed so far unresolved.

Map

stackoverflow find a lot of the same problems ,
but most of the answer is not an indexed notes, choose other ways to manage the index.

These results are not satisfactory.

Try to view the spring-data-mongo mechanism, navigate to MongoPersistentEntityIndexCreator categories:

  1. Init method will be to create an index based on MappingContext (entity mapping context) in an existing entity
. 1  public MongoPersistentEntityIndexCreator (MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory,
 2              IndexResolver indexResolver) {
 . 3          ...
 . 4          // The entity has created 
. 5          for (MongoPersistentEntity <?> Entity: mappingContext.getPersistentEntities ()) {
 . 6              checkForIndexes (Entity);
 . 7          }
 8      }

2. Upon receiving the MappingContextEvent, creating an index corresponding to the entity

 1     public void onApplicationEvent(MappingContextEvent<?, ?> event) {
 2 
 3         if (!event.wasEmittedBy(mappingContext)) {
 4             return;
 5         }
 6 
 7         PersistentEntity<?, ?> entity = event.getPersistentEntity();
 8 
 9         // Double check type as Spring infrastructure does not consider nested generics
10         if (entity instanceof MongoPersistentEntity) {
11             //创建单个实体索引
12             checkForIndexes((MongoPersistentEntity<?>) entity);
13         }
14     }

MongoPersistentEntityIndexCreator by MongoTemplate introduced, as follows:

 1     public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) {
 2 
 3         Assert.notNull(mongoDbFactory);
 4 
 5         this.mongoDbFactory = mongoDbFactory;
 6         this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
 7         this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter;
 8         ...
 9 
10         // We always have a mapping context in the converter, whether it's a simple one or not
11         mappingContext = this.mongoConverter.getMappingContext();
12         // We create indexes based on mapping events
13         if (null != mappingContext && mappingContext instanceof MongoMappingContext) {
14             indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory);
15             eventPublisher = new MongoMappingEventPublisher(indexCreator);
16             if (mappingContext instanceof ApplicationEventPublisherAware) {
17                 ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
18             }
19         }
20     }
21 
22 
23     ...
24     //MongoTemplate实现了 ApplicationContextAware,当ApplicationContext被实例化时被感知
25     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
26 
27         prepareIndexCreator(applicationContext);
28 
29         eventPublisher = applicationContext;
30         if(mappingContext the instanceof ApplicationEventPublisherAware) {
 31 is              // MappingContext as an event source, to release the ApplicationContext 
32              ((ApplicationEventPublisherAware) mappingContext) .setApplicationEventPublisher (eventPublisher);
 33 is          }
 34 is          ResourceLoader = applicationContext;
 35      }
 36  
37 [      ...
 38 is      // injection event listener 
39      Private  void prepareIndexCreator (the ApplicationContext context) {
 40  
41 is          String [] = indexCreators context.getBeanNamesForType (MongoPersistentEntityIndexCreator. class );
 42 is 
43         for (String creator : indexCreators) {
44             MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class);
45             if (creatorBean.isIndexCreatorFor(mappingContext)) {
46                 return;
47             }
48         }
49 
50         if (context instanceof ConfigurableApplicationContext) {
51             //使 IndexCreator 监听 ApplicationContext的事件
52             ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator);
53         }
54     }

Thus, MongoTemplate during initialization, the first through MongoConverter into MongoMappingContext,
then completes a series of initialization, the entire process is as follows:

  • Examples of MongoTemplate;
  • Examples of MongoConverter;
  • Examples of MongoPersistentEntityIndexCreator;
  • Initialization index (by MappingContext existing entity);
  • Repository Initialization -> MappingContext release of mapping the event;
  • ApplicationContext event notifications to IndexCreator;
  • IndexCreator create an index

In an example of the process, without any configuration you can prevent the creation of an index.

Fourth, to solve the problem

From the above analysis, the key question can be found in IndexCreator, can provide a custom implementation of it, the answer is yes!

To achieve the following elements

  1. Achieve a IndexCreator, inheritable MongoPersistentEntityIndexCreator, remove the ability to create an index;
  2. MongoConverter instantiation and MongoTemplate, with an empty index initialization MongoMappingContext avoid objects;
  3. The custom IndexCreator be registered as a Bean, so that when prepareIndexCreator method of execution,
    the original MongoPersistentEntityIndexCreator not listen ApplicationContext events
  4. IndexCreator realized ApplicationContext listens, takes over MappingEvent event handling.

Bean instantiation

 1     @Bean
 2     public MongoMappingContext mappingContext() {
 3         return new MongoMappingContext();
 4     }
 5 
 6     // 使用 MappingContext 实例化 MongoTemplate
 7     @Bean
 8     public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoMappingContext mappingContext) {
 9         MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory),
10                 mappingContext);
11         converter.setTypeMapper(new DefaultMongoTypeMapper(null));
12 
13         MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
14 
15         return mongoTemplate;
16     }

Custom IndexCreator

 1     // 自定义IndexCreator实现
 2     @Component
 3     public static class CustomIndexCreator extends MongoPersistentEntityIndexCreator {
 4 
 5         // 构造器引用MappingContext
 6         public CustomIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) {
 7             super(mappingContext, mongoDbFactory);
 8         }
 9 
10         public void onApplicationEvent(MappingContextEvent<?, ?> event) {
11             PersistentEntity<?, ?> entity = event.getPersistentEntity ();
 12 is  
13 is              // obtained Mongo entity classes 
14              IF (the instanceof MongoPersistentEntity Entity) {
 15                  the System. OUT .println ( " Detected MongoEntity " + entity.getName ());
 16                  
. 17                  // may be implemented indexing process .. 
18              }
 19          }
 20      }

Here CustomIndexCreator inherited MongoPersistentEntityIndexCreator , will automatically take over the monitor MappingContextEvent events.
In business process implementation can be done according to the index!

summary

spring-data-mongo provides a great convenience, but is still insufficient in terms of flexibility support. The above method is actually somewhat vague, not mentioned in official documents in this way.
ORM-Mapping Framework Schema mapping process in achieving parity level to be considered, such as Hibernate will provide none / create / update / validation choice, after all, more friendly for developers.
Look forward to spring-data-mongo in the subsequent evolution can improve Schema management capabilities as soon as possible!

Author: United States Code division

Guess you like

Origin www.cnblogs.com/huaweicloud/p/11867342.html