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

∥☆過路亽.° 提交于 2019-12-05 11:42:39
przem

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.

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. 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" }
  • Adding generated ObjectId to the entity for new documents

     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

Cuong Le

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.

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

To save new document, just use new customSave

@Autowired
DocumentRepo docRepo;

docRepo.customSave(doc);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!