【问题标题】:How to prevent Spring from trying to create indexes during MongoDB transactions?如何防止 Spring 在 MongoDB 事务期间尝试创建索引?
【发布时间】:2019-10-25 06:28:26
【问题描述】:

我正在尝试将 Spring Data Mongo 与事务一起使用。我最初遇到了一个问题,由于 Spring 在第一次插入文档期间尝试创建集合和/或索引,我的插入会失败。此后,我通过在任何事务开始之前在启动时创建所有集合和索引来解决该问题。但是,我仍然使用 Spring Data 注释来定义索引(即,@Indexed@CompoundIndexes 等)。但是,即使我已经创建了所有索引,Spring 仍在尝试在 mongo 处理期间确保/创建索引。

在我的情况下,我对我的数据类型使用继承。即,BasicUnit 其他单元的扩展。如果我去存储特定类型的单元,并且这是我在当前应用程序运行期间第一次尝试保存该类型的对象,Spring 不会识别它,并为其创建一个新的PersistentEntity。作为创建新 PersistentEntity 的一部分,Spring 发布了一个事件,MongoPersistentEntityIndexCreator 捕获了该事件,这反过来又尝试确保创建所有索引,从而引发异常。

这是一个例外:

Caused by: com.mongodb.MongoCommandException: Command failed with error 263 (OperationNotSupportedInTransaction): 'It is illegal to run command createIndexes in a multi-document transaction.' on server 127.0.0.1:27017. The full response is { "operationTime" : { "$timestamp" : { "t" : 1560198052, "i" : 1 } }, "ok" : 0.0, "errmsg" : "It is illegal to run command createIndexes in a multi-document transaction.", "code" : 263, "codeName" : "OperationNotSupportedInTransaction", "$clusterTime" : { "clusterTime" : { "$timestamp" : { "t" : 1560198052, "i" : 1 } }, "signature" : { "hash" : { "$binary" : "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", "$type" : "00" }, "keyId" : { "$numberLong" : "0" } } } }
    at com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:179)
    at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:293)
    at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:255)
    at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:99)
    at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:444)
    at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:72)
    at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:200)
    at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:269)
    at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:131)
    at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:123)
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:242)
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:233)
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:170)
    at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:163)
    at com.mongodb.operation.CreateIndexesOperation$1.call(CreateIndexesOperation.java:174)
    at com.mongodb.operation.CreateIndexesOperation$1.call(CreateIndexesOperation.java:169)
    at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:453)
    at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:415)
    at com.mongodb.operation.CreateIndexesOperation.execute(CreateIndexesOperation.java:169)
    at com.mongodb.operation.CreateIndexesOperation.execute(CreateIndexesOperation.java:70)
    at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:193)
    at com.mongodb.client.internal.MongoCollectionImpl.executeCreateIndexes(MongoCollectionImpl.java:805)
    at com.mongodb.client.internal.MongoCollectionImpl.createIndexes(MongoCollectionImpl.java:800)
    at com.mongodb.client.internal.MongoCollectionImpl.createIndexes(MongoCollectionImpl.java:793)
    at com.mongodb.client.internal.MongoCollectionImpl.createIndex(MongoCollectionImpl.java:778)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:246)
    at org.springframework.data.mongodb.SessionAwareMethodInterceptor.invoke(SessionAwareMethodInterceptor.java:123)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy202.createIndex(Unknown Source)
    at org.springframework.data.mongodb.core.DefaultIndexOperations.lambda$ensureIndex$0(DefaultIndexOperations.java:135)
    at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:545)

这里是调用 ensureIndex 的调用堆栈:

DefaultIndexOperations.lambda$ensureIndex$0(IndexDefinition, MongoCollection) line: 135 
1894975953.doInCollection(MongoCollection) line: not available  
MongoTemplate.execute(String, CollectionCallback<T>) line: 545  
DefaultIndexOperations.execute(CollectionCallback<T>) line: 218 
DefaultIndexOperations.ensureIndex(IndexDefinition) line: 121   
MongoPersistentEntityIndexCreator.createIndex(MongoPersistentEntityIndexResolver$IndexDefinitionHolder) line: 145   
MongoPersistentEntityIndexCreator.checkForAndCreateIndexes(MongoPersistentEntity<?>) line: 135  
MongoPersistentEntityIndexCreator.checkForIndexes(MongoPersistentEntity<?>) line: 127   
MongoPersistentEntityIndexCreator.onApplicationEvent(MappingContextEvent<?,?>) line: 111    
MongoPersistentEntityIndexCreator.onApplicationEvent(ApplicationEvent) line: 54 
SimpleApplicationEventMulticaster.doInvokeListener(ApplicationListener, ApplicationEvent) line: 172 
SimpleApplicationEventMulticaster.invokeListener(ApplicationListener<?>, ApplicationEvent) line: 165    
SimpleApplicationEventMulticaster.multicastEvent(ApplicationEvent, ResolvableType) line: 139    
AnnotationConfigWebApplicationContext(AbstractApplicationContext).publishEvent(Object, ResolvableType) line: 398    
AnnotationConfigWebApplicationContext(AbstractApplicationContext).publishEvent(ApplicationEvent) line: 355  
MongoMappingContext(AbstractMappingContext<E,P>).addPersistentEntity(TypeInformation<?>) line: 405  
MongoMappingContext(AbstractMappingContext<E,P>).getPersistentEntity(TypeInformation<?>) line: 248  
MongoMappingContext(AbstractMappingContext<E,P>).getPersistentEntity(Class<?>) line: 191    
MongoMappingContext(AbstractMappingContext<E,P>).getPersistentEntity(Class) line: 85    
MongoMappingContext(MappingContext<E,P>).getRequiredPersistentEntity(Class<?>) line: 73 
EntityOperations$AdaptibleMappedEntity<T>.of(T, MappingContext<MongoPersistentEntity<?>,MongoPersistentProperty>, ConversionService) line: 600  
EntityOperations$AdaptibleMappedEntity<T>.access$100(Object, MappingContext, ConversionService) line: 580   
EntityOperations.forEntity(T, ConversionService) line: 105  
MongoTemplate.doInsert(String, T, MongoWriter<T>) line: 1237

你可以看到Spring在哪里尝试get持久化实体,然后最终决定add,然后触发事件,然后通过尝试处理事件创建索引。

我需要知道如何防止 Spring 在事务期间尝试创建这些索引。

我考虑过的事情

  1. 尝试弄清楚如何预先注册我所有的持久实体, 但除了手动保存列表之外,我不确定如何做到这一点,或者 编写代码以扫描类路径以查找所有类 由@Document 注释或扩展这样的类。
  2. 完全放弃 Spring 的索引注解,只使用显式调用 ensureIndex 为我需要的每个索引。

这些解决方案对我来说都不是很有吸引力。

我很难相信没有其他人在使用 Spring Data Mongo 和 Transactions 时遇到过类似的索引问题,所以你们中的任何人都知道这个问题的解决方案,我很想听听。

谢谢。

【问题讨论】:

    标签: mongodb spring-data-mongodb spring-transactions


    【解决方案1】:
    Automatic index creation will be disabled by default as of Spring Data MongoDB 3.x.     Please use 'MongoMappingContext#setAutoIndexCreation(boolean)' or override 'MongoConfigurationSupport#autoIndexCreation()' to be explicit.  However, we recommend setting up indices manually in an application ready block. You may use index derivation there as well.
    
    > -----------------------------------------------------------------------------------------
    > @EventListener(ApplicationReadyEvent.class)
    > public void initIndicesAfterStartup() {
    >
    >     IndexOperations indexOps = mongoTemplate.indexOps(DomainType.class);
    >
    >     IndexResolver resolver = new MongoPersistentEntityIndexResolver(mongoMappingContext);
    >     resolver.resolveIndexFor(DomainType.class).forEach(indexOps::ensureIndex);
    > }
    > -----------------------------------------------------------------------------------------
    

    试试这个

    @EventListener(ApplicationReadyEvent.class)
    public void initIndicesAfterStartup() {    
      log.info("Mongo InitIndicesAfterStartup init");
      var init = System.currentTimeMillis();
    
      var mappingContext = this.mongoConverter.getMappingContext();
    
      if (mappingContext instanceof MongoMappingContext) {
        var mongoMappingContext = (MongoMappingContext) mappingContext;
    
        for (BasicMongoPersistentEntity<?> persistentEntity : mongoMappingContext.getPersistentEntities()) {
          var clazz = persistentEntity.getType();
          if (clazz.isAnnotationPresent(Document.class)) {
            var indexOps = mongoTemplate.indexOps(clazz);
            var resolver = new MongoPersistentEntityIndexResolver(mongoMappingContext);                    
            resolver.resolveIndexFor(clazz).forEach(indexOps::ensureIndex);
          }
        }
      }
      log.info("Mongo InitIndicesAfterStartup take: {}", (System.currentTimeMillis() - init));
    }
    

    记住 var 仅适用于 java 11+

    【讨论】:

    【解决方案2】:

    原来是我的java包安排造成的。默认情况下,Spring MongoDB 将扫描并注册与 MongoConfiguration 相同的包中的所有 @Document@Persistent 实体。但是,我碰巧将该类与我的所有实体放在不同的包中。

    我在 MongoConfiguration 中覆盖了 MongoConfigurationSupport#getMappingBasePackages,然后 Spring 能够找到并“预注册”我的所有实体,因此它们不再在事务中被“发现”。

    public class MongoConfiguration extends AbstractMongoConfiguration {
    
    ....
    
        /* (non-Javadoc)
         * @see org.springframework.data.mongodb.config.MongoConfigurationSupport#getMappingBasePackages()
         */
        @Override
        protected Collection<String> getMappingBasePackages() {
            java.util.List<String> packages = new ArrayList<>(1);
            packages.add("my.entity.base.package");
            return packages;
        }
    }
    

    【讨论】:

    • 从 Spring Data MongoDB 2.2 开始,可以通过覆盖 boolean autoIndexCreation() 来配置索引创建
    猜你喜欢
    • 2020-12-06
    • 1970-01-01
    • 2017-12-09
    • 2019-04-26
    • 1970-01-01
    • 2017-10-20
    • 2014-11-22
    • 2014-03-02
    • 2015-12-30
    相关资源
    最近更新 更多