当前位置: 首页 > 知识库问答 >
问题:

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

阳建弼
2023-03-14

我在数据库中有一个集合“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, “code” : 61, “errmsg” : “命令中的查询必须以单个分片键为目标”,“$err” : “命令中的查询必须以单个分片键为目标” }

这是我的文档对象

@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> { }

以及我试图更新的值

    < Li > Value:doc:{ " _ id ":" 12831221 Wada ee 23 "," dNumber" : "115 "," processed": true }

我尝试执行的函数:

@Autowired
DocumentRepo docRepo;

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

注意:我在dNumber字段上启用了分片。并且我能够成功地在非关系型数据库工具上使用本机查询进行更新。我还能够在非分片集合上执行存储库保存操作。

更新:我能够通过使用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个答案

张成济
2023-03-14

我正在通过创建自定义存储库来破解它:

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> { }

要保存新文档,只需使用新建自定义保存

@Autowired
DocumentRepo docRepo;

docRepo.customSave(doc);
马坚白
2023-03-14

按照自定义存储库方法,我得到了一个错误,因为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数据mongodb:1.9.11

  • 酆鸿哲
    2023-03-14

    我有同样的问题,通过以下黑客解决:

    @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将文档字段标记为分片并将其自动添加到查询中。

     类似资料:
    • 有人知道在Mongo3中更新封顶收藏的方法吗。2.我让它在2分钟内工作。我更新了一个收藏,基本上删除了它的所有内容,所以我知道它已经被处理过了。这样就会过时。 当我在3.2中执行同样的操作时,我在命令行上会出现以下错误。 无法更改封顶集合中文档的大小:318!=40 在这里,你可以看到我正在将文档从318字节缩减到40字节。 有办法做到这一点吗?

    • 我们有一个相对简单的分片MongoDB设置:4个分片,每个分片是一个副本集,至少有3个成员。每个集合都由从大量文件加载的数据组成;每个文件都被赋予一个单调递增的ID,并且根据ID的哈希完成分片。 我们的大部分产品都在按预期工作。然而,我有一个集合似乎没有正确地将块分布到各个碎片上。在创建索引之前,集合加载了大约30GB的数据,并且进行了分片,但是据我所知,这并不重要。以下是该集合的统计数据: 这个

    • 主要内容:MongoDB 中的分片,分片实例分片是跨多台机器存储数据的过程,它是 MongoDB 满足数据增长需求的方法。随着数据的不断增加,单台机器可能不足以存储全部数据,也无法提供足够的读写吞吐量。通过分片,您可以添加更多计算机来满足数据增长和读/写操作的需求。 为什么要分片? 在复制中,所有写操作都将转到主节点; 对延迟敏感的查询仍会转到主查询; 单个副本集限制为 12 个节点; 当活动数据集很大时,会出现内存不足; 本地磁盘不够大;

    • 我在localhost上设置了一个分片的mongo db环境,有3个配置服务器、2个分片的mongo实例和一个mongos。 集群启动后,我运行以下命令序列: 我启用数据库进行分片,并创建一个索引等。 以上所有操作的结果都是成功的。 但是一旦我做到了:db.foo.stats() 我看到所有的数据都在一个分片中结束,而没有被分发。和运行 生产: 然而,有趣的是,如果我从一个空白集合开始,并在向其中

    • 我使用Spring Boot和Spring Data MongoDB与底层分片MongoDB集群进行接口。我的Spring Boot应用程序通过路由器访问集群。 使用Spring Data MongoDB,可以通过指定对象持久化到的集合,或者默认为类名(第一个字母小写)。这些藏品不需要预先存在;它们可以在运行时创建。 要在MongoDB中分片集合,您需要 1-在数据库上启用分片: 2-在分片数据库

    • 我想从配置文件中部署一个mongodb副本集,该副本集包含1个主副本和2个次副本,如下所示: 主节点的第一个配置文件 我也得到了同样的错误 我不知道我需要做什么esle配置来解决这个问题,请帮助我,非常感谢。