Spring Boot无法更新Azure Cosmos db(MongoDb)上的分片集合

时间:2019-03-14 18:26:09

标签: java mongodb spring-boot sharding

我在数据库中存在一个集合“ documentDev”,其分片键为“ dNumber” 样本文档:

{
"_id" : "12831221wadaee23",
"dNumber" : "115",
"processed": false
}

如果我尝试使用-

之类的命令通过任何查询工具来更新此文档,
db.documentDev.update({
  "_id" : ObjectId("12831221wadaee23"),
  "dNumber":"115"
},{
    $set:{"processed": true}}, 
{ multi: false, upsert: false}
)}`

它将正确更新文档。 但是如果我确实使用spring boot的mongorepository命令,例如     DocumentRepo.save(Object) 会引发异常

  
      
  • 由com.mongodb.MongoCommandException引起:命令失败,错误61:服务器上的查询“命令中的查询必须以单个分片键为目标” by3prdddc01-docdb-3.documents.azure.com:10255。完整的响应为{“ _t”:“ OKMongoResponse”,“ ok”:0,“代码”:61,“ errmsg”:“命令中的查询必须以单个分片键为目标”,“ $ err”:“命令中的查询必须以单个分片键为目标”}
  •   

这是我的DocumentObject:

@Document(collection = "documentDev")
public class DocumentDev
{
@Id
private String id;
private String dNumber;
private String fileName;
private boolean processed;
}

这是我的存储库类-

@Repository
public interface DocumentRepo extends MongoRepository<DocumentDev, 
String> { }

和我试图更新的价值

  
      
  • 值:doc:   {   “ _id”:“ 12831221wadaee23”,   “ dNumber”:“ 115”,   “已处理”:是   }
  •   

我要执行的功能:

@Autowired
DocumentRepo docRepo;

docRepo.save(doc); // Fails to execute

注意:我在dNumber字段上启用了分片。而且我能够在NoSQL Tool上使用本机查询成功进行更新。 我还能够对非分片集合执行存储库保存操作。

更新:我可以通过使用MongoTemplate创建本机查询来更新文档-我的查询如下所示-

public DocumentDev updateProcessedFlag(DocumentDev request) {
    Query query = new Query();
    query.addCriteria(Criteria.where("_id").is(request.getId()));
    query.addCriteria(Criteria.where("dNumber").is(request.getDNumber()));
    Update update = new Update();
    update.set("processed", request.isProcessed());
    mongoTemplate.updateFirst(query, update, request.getClass());
    return request;
}

但这不是通用的解决方案,因为任何其他字段可能都已更新,而我的文档也可能具有其他字段。

3 个答案:

答案 0 :(得分:0)

我遇到了同样的问题,并通过以下hack解决了该问题:

@Configuration
public class ReactiveMongoConfig {

@Bean
public ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory,
                                                   MongoConverter converter,
                                                   MyService service) {
    return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory, converter) {
        @Override
        protected Mono<UpdateResult> doUpdate(String collectionName, Query query, UpdateDefinition update, Class<?> entityClass, boolean upsert, boolean multi) {
            query.addCriteria(new Criteria("shardKey").is(service.getShardKey()));
            return super.doUpdate(collectionName, query, update, entityClass, upsert, multi);
        }
    };
}

}

最好有一个@ShardKey注释将文档字段标记为分片,并将其添加到自动查询中。

答案 1 :(得分:0)

我通过创建自定义存储库来对此进行入侵:

public interface CosmosCustomRepository<T> {
    void customSave(T entity);
    void customSave(T entity, String collectionName);
}

此存储库的实现

public class CosmosCustomRepositoryImpl<T> implements CosmosCustomRepository<T> {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public void customSave(T entity) {
        mongoTemplate.upsert(createQuery(entity), createUpdate(entity), entity.getClass());
    }

    @Override
    public void customSave(T entity, String collectionName) {
        mongoTemplate.upsert(createQuery(entity), createUpdate(entity), collectionName);
    }

    private Update createUpdate(T entity) {
        Update update = new Update();
        for (Field field : entity.getClass().getDeclaredFields()) {
            try {
                field.setAccessible(true);
                if (field.get(entity) != null) {
                    update.set(field.getName(), field.get(entity));
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return update;
    }

    private Query createQuery(T entity) {
        Criteria criteria = new Criteria();
        for (Field field : entity.getClass().getDeclaredFields()) {
            try {
                field.setAccessible(true);
                if (field.get(entity) != null) {
                    if (field.getName().equals("id")) {
                        return new Query(Criteria.where("id").is(field.get(entity)));
                    }
                    criteria.and(field.getName()).is(field.get(entity));
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return new Query(criteria);
    }
}

您的DocumentRepo将扩展这个新的自定义存储库。

@Repository
public interface DocumentRepo extends MongoRepository<DocumentDev, String>, CosmosCustomRepository<DocumentDev> { }

要保存新文档,只需使用新的customSave

@Autowired
DocumentRepo docRepo;

docRepo.customSave(doc);

答案 2 :(得分:0)

在使用自定义存储库方法之后,我遇到了一个错误,因为spring希望自定义实现{EntityName} CustomRepositoryImpl中可以使用Cosmos实体,因此我重命名了实现。我还添加了以下代码:

  • 实体继承字段的情况
  • 分片键并不总是ID,我们应该将其与ID一起添加:{“ shardkeyName”:“ shardValue”}
  • 将生成的ObjectId添加到新文档的实体中

     public class DocumentRepositoryImpl<T> implements CosmosRepositoryCustom<T> {
    
        @Autowired
        protected MongoTemplate mongoTemplate;
    
        @Override
        public T customSave(T entity) {
            WriteResult writeResult = mongoTemplate.upsert(createQuery(entity), createUpdate(entity), entity.getClass());
            setIdForEntity(entity,writeResult);
            return entity;
        }
    
        @Override
        public T customSave(T entity, String collectionName) {
            WriteResult writeResult = mongoTemplate.upsert(createQuery(entity), createUpdate(entity), collectionName);
            setIdForEntity(entity,writeResult);
            return entity;
        }
    
        @Override
        public void customSave(List<T> entities) {
            if(CollectionUtils.isNotEmpty(entities)){
                entities.forEach(entity -> customSave(entity));
            }
        }
    
        public <T> Update createUpdate(T entity){
            Update update = new Update();
            for (Field field : getAllFields(entity)) {
                try {
                    field.setAccessible(true);
                    if (field.get(entity) != null) {
                        update.set(field.getName(), field.get(entity));
                    }
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    LOGGER.error("Error creating update for entity",e);
                }
            }
            return update;
        }
    
        public <T> Query createQuery(T entity) {
            Criteria criteria = new Criteria();
            for (Field field : getAllFields(entity)) {
                try {
                    field.setAccessible(true);
                    if (field.get(entity) != null) {
                        if (field.getName().equals("id")) {
                            Query query = new Query(Criteria.where("id").is(field.get(entity)));
                            query.addCriteria(new Criteria(SHARD_KEY_NAME).is(SHARD_KEY_VALUE));
                            return query;
                        }
                        criteria.and(field.getName()).is(field.get(entity));
                    }
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    LOGGER.error("Error creating query for entity",e);
                }
            }
            return new Query(criteria);
        }
    
        private <T> List<Field> getAllFields(T entity) {
            List<Field> fields = new ArrayList<>();
            fields.addAll(Arrays.asList(entity.getClass().getDeclaredFields()));
            Class<?> c = entity.getClass().getSuperclass();
            if(!c.equals(Object.class)){
                fields.addAll(Arrays.asList(c.getDeclaredFields()));
            }
            return fields;
        }
    
        public <T> void setIdForEntity(T entity,WriteResult writeResult){
            if(null != writeResult && null != writeResult.getUpsertedId()){
                Object upsertId = writeResult.getUpsertedId();
                entity.setId(upsertId.toString());
            }
        }
    }
    

我正在使用spring-boot-starter-mongodb:1.5.1和spring-data-mongodb:1.9.11