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:
- 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
- Achieve a IndexCreator, inheritable MongoPersistentEntityIndexCreator, remove the ability to create an index;
- MongoConverter instantiation and MongoTemplate, with an empty index initialization MongoMappingContext avoid objects;
- The custom IndexCreator be registered as a Bean, so that when prepareIndexCreator method of execution,
the original MongoPersistentEntityIndexCreator not listen ApplicationContext events - 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