简体   繁体   English

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

[英]Spring Boot not able to update sharded collection on azure cosmos db(MongoDb)

I have a collection "documentDev" present in the database with sharding key as 'dNumber' Sample Document :我在数据库中有一个集合“documentDev”,分片键为“dNumber”示例文档:

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

If I try to update this document through any query tool using a command like -如果我尝试使用以下命令通过任何查询工具更新此文档 -

db.documentDev.update({
  "_id" : ObjectId("12831221wadaee23"),
  "dNumber":"115"
},{
    $set:{"processed": true}}, 
{ multi: false, upsert: false}
)}`

It updates the document properly.它正确更新文档。 But if I do use spring boot's mongorepository command like DocumentRepo.save(Object) it throws an exception但是,如果我确实使用 Spring Boot 的 mongorepository 命令,例如 DocumentRepo.save(Object) 它会引发异常

  • Caused by: com.mongodb.MongoCommandException: Command failed with error 61: 'query in command must target a single shard key' on server by3prdddc01-docdb-3.documents.azure.com:10255.引起:com.mongodb.MongoCommandException:命令失败,错误 61:'query in command must target a single shard key' on server by3prdddc01-docdb-3.documents.azure.com:10255。 The full response is { "_t" : "OKMongoResponse", "ok" : 0, "code" : 61, "errmsg" : "query in command must target a single shard key", "$err" : "query in command must target a single shard key" }完整的响应是 { "_t" : "OKMongoResponse", "ok" : 0, "code" : 61, "errmsg" : "query in command must target a single shard key", "$err" : "query in command必须针对单个分片键"}

This is my DocumentObject:这是我的文档对象:

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

This is my Repository Class -这是我的存储库类 -

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

and value i am trying to update和价值我正在尝试更新

  • Value : doc : { "_id" : "12831221wadaee23", "dNumber" : "115", "processed": true }值:文档:{“_id”:“12831221wadaee23”,“dNumber”:“115”,“已处理”:真}

the function I am trying to execute :我试图执行的功能:

@Autowired
DocumentRepo docRepo;

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

Note: I have sharding enabled on dNumber field.注意:我在 dNumber 字段上启用了分片。 And I am successfully able to update using Native queries on NoSQL Tool.我能够成功地在 NoSQL 工具上使用本机查询进行更新。 I was also able to execute the Repository save operation on Non sharded collection.我还能够对非分片集合执行存储库保存操作。

Update: I am able to update the document by creating native query using MongoTemplate - My Query looks like this -更新:我可以通过使用 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;
}

But this is not a generic solution as any other field might have update and my document may have other fields as well.但这不是通用解决方案,因为任何其他字段都可能有更新,我的文档也可能有其他字段。

I had the same issue, solved with following 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);
            }
        };
    }
}

Would be nice to have an annotation @ShardKey to mark document field as shard and have it added to query automatically.有一个注释@ShardKey 将文档字段标记为分片并将其自动添加到查询中会很好。

i am hacking this by create a custom repository:我通过创建一个自定义存储库来破解这个:

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

the implement for this repository:此存储库的实现:

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);
    }
}

your DocumentRepo will extends this new custom repository.您的DocumentRepo将扩展这个新的自定义存储库。

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

To save new document, just use new customSave要保存新文档,只需使用 new customSave

@Autowired
DocumentRepo docRepo;

docRepo.customSave(doc);

Following the custom repository approach, I got an error because spring is expecting a Cosmos entity to be available in the custom implementation {EntityName}CustomRepositoryImpl, so I renamed the implementation.遵循自定义存储库方法,我收到一个错误,因为 spring 期望在自定义实现 {EntityName}CustomRepositoryImpl 中可以使用 Cosmos 实体,因此我重命名了该实现。 I also added code for:我还添加了以下代码:

  • The case when entity has inherited fields实体继承字段的情况
  • Shard key is not always the Id, we should add it along with the id: { "shardkeyName": "shardValue" }分片键并不总是 Id,我们应该将它与 id 一起添加:{ "shardkeyName": "shardValue" }
  • Adding generated ObjectId to the entity for new documents将生成的 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()); } } }

I am using spring-boot-starter-mongodb:1.5.1 with spring-data-mongodb:1.9.11我正在使用 spring-boot-starter-mongodb:1.5.1 和 spring-data-mongodb:1.9.11

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM