Почему я получаю CodecConfigurationException при выполнении запроса NearSphere с использованием GeoJsonPoint?

Эта проблема меня озадачивает. Итак, у меня есть UserModel, где я храню последние известные координаты пользователя, используя объект org.springframework.data.mongodb.core.geo.GeoJsonPoint, который переводится в

"lastKnownCoordinates": {
        "type": "Point",
        "coordinates": [77.596503, 12.966267]
    }

У меня есть коллекции экскурсий, которые сопоставляются с объектом ExcursionsModel. Каждая экскурсия имеет свойство геолокации, которое хранит свое местоположение, используя спецификацию GeoJsonPoint, например:

"geolocation": {
        "coordinates": [73.739978, 15.606188],
        "type": "Point"
    }

Теперь, когда пользователь пытается добавить ближайший фильтр, чтобы получить экскурсии на указанное расстояние, я добавляю $nearSphere критерии к объекту запроса (org.springframework.data.mongodb.core.query.Query), как это

if (userRequest.getDistInMeters() != null) {
    query.addCriteria(Criteria.where("geolocation")
                        .nearSphere(userModel.getLastKnownCoordinates())
                        .maxDistance(userRequest.getDistInMeters())
    );
}

Технически это должно означать следующее:

...
"geolocation" : {
  $nearSphere: {
     $geometry: {
        type : "Point",
        coordinates : [ 77.596503, 12.966267 ]
     },
     $maxDistance: 10000
  }
}...

(если distInMeters = 10000)

Но, я это скорее переводится как:

...
"geolocation" : { 
    "$nearSphere" : { 
        "$geometry" : { 
            "$java" : Point [x=-3.703790, y=40.416775] 
        } 
    } 
}...

Обратите внимание, что преобразование GeoJsonPoint неверно. Я также получаю следующее исключение

org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class org.springframework.data.mongodb.core.geo.GeoJsonPoint.

Полная стенограмма вывода:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class org.springframework.data.mongodb.core.geo.GeoJsonPoint.] with root cause
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class org.springframework.data.mongodb.core.geo.GeoJsonPoint.
    at org.bson.internal.CodecCache.getOrThrow(CodecCache.java:57)
    at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:64)
    at org.bson.internal.ChildCodecRegistry.get(ChildCodecRegistry.java:52)
    at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:197)
    at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:212)
    at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:195)
    at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:212)
    at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:195)
    at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:212)
    at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:154)
    at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
    at org.bson.BsonDocumentWrapper.getUnwrapped(BsonDocumentWrapper.java:195)
    at org.bson.BsonDocumentWrapper.isEmpty(BsonDocumentWrapper.java:115)
    at com.mongodb.internal.operation.DocumentHelper.putIfNotNullOrEmpty(DocumentHelper.java:43)
    at com.mongodb.internal.operation.FindOperation.getCommand(FindOperation.java:792)
    at com.mongodb.internal.operation.FindOperation.access$1600(FindOperation.java:77)
    at com.mongodb.internal.operation.FindOperation$4.create(FindOperation.java:858)
    at com.mongodb.internal.operation.CommandOperationHelper.executeCommandWithConnection(CommandOperationHelper.java:219)
    at com.mongodb.internal.operation.FindOperation$1.call(FindOperation.java:631)
    at com.mongodb.internal.operation.FindOperation$1.call(FindOperation.java:625)
    at com.mongodb.internal.operation.OperationHelper.withReadConnectionSource(OperationHelper.java:462)
    at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:625)
    at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:77)
    at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:190)
    at com.mongodb.client.internal.MongoIterableImpl.execute(MongoIterableImpl.java:135)
    at com.mongodb.client.internal.MongoIterableImpl.iterator(MongoIterableImpl.java:92)
    at com.example.comercial.backend.dao.ExcursionDao.getAllExcursions(ExcursionsDao.java:140)
    at com.example.comercial.backend.service.ExcursionsService.getAllExcursions(ExcursionsService.java:70)
    at com.example.comercial.backend.controller.ExcursionsController.getAllExcursions(ExcursionsController.java:38)
    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.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:289)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

Фрагмент кода из класса ExcursionsDao, где возникает исключение:

...
FindIterable<Document> findIterable = mongoTemplate.getCollection("excursions")
                .find(query.getQueryObject())
                .skip(pageable.getPageSize() * (pageable.getPageNumber() - 1))
                .limit(pageable.getPageSize());

if (userRequest.getOrderBy() != null && userRequest.getSortOrder() != null) {
    if (userRequest.getSortOrder().getSortOrder().equals(SortOrder.ASC.getSortOrder())) {
        findIterable.sort(Sorts.ascending(userRequest.getOrderBy().getOrderBy()));
    } else {
        findIterable.sort(Sorts.descending(userRequest.getOrderBy().getOrderBy()));
    }
}

ArrayList<ExcursionModel> excursionModelList = new ArrayList<>();

for (Document document : findIterable) {             // Line 140 where the exception is thrown
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    excursionModelList.add(objectMapper.convertValue(document, ExcursionModel.class));
}

Я использую значения по умолчанию для соединения монго, которое фактически инициализируется с помощью GeoJsonCodecProvider.

Из класса MongoClientSettings:

public final class MongoClientSettings {
    private static final CodecRegistry DEFAULT_CODEC_REGISTRY =
            fromProviders(asList(new ValueCodecProvider(),
                    new BsonValueCodecProvider(),
                    new DBRefCodecProvider(),
                    new DBObjectCodecProvider(),
                    new DocumentCodecProvider(new DocumentToDBRefTransformer()),
                    new IterableCodecProvider(new DocumentToDBRefTransformer()),
                    new MapCodecProvider(new DocumentToDBRefTransformer()),
                    new GeoJsonCodecProvider(),
                    new GridFSFileCodecProvider(),
                    new Jsr310CodecProvider(),
                    new BsonCodecProvider()));

Какие альтернативы я пробовал, но они не сработали

1. Я написал следующий сериализатор (взято из ответа SO, который я забыл) для объекта GeoJsonPoint

public class GeoJsonPointSerializer extends JsonSerializer<GeoJsonPoint> {

    @Override
    public void serialize(final GeoJsonPoint value, final JsonGenerator gen, final SerializerProvider serializers)
            throws IOException {
        gen.writeStartObject();
        gen.writeStringField("type", value.getType());
        gen.writeArrayFieldStart("coordinates");
        gen.writeObject(value.getCoordinates());
        gen.writeEndArray();
        gen.writeEndObject();
    }

}

и использовал его для свойства геолокации в ExcursionsModel вот так

@JsonSerialize(using = GeoJsonPointSerializer.class)
@GeoSpatialIndexed(name = "geoIndex", type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPoint geolocation;

2. Я создал экземпляр CodecRegistry и добавил к нему PointCodec вместе с реестрами кодеков по умолчанию из MongoClientSettings, а затем инициализировал класс FilterIterable с этим реестром кодеков.

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
                MongoClientSettings.getDefaultCodecRegistry(),
                CodecRegistries.fromCodecs(new PointCodec(MongoClientSettings.getDefaultCodecRegistry()))
        );

FindIterable<Document> findIterable = mongoTemplate.getCollection("excursions").withCodecRegistry(codecRegistry)
                .find(query.getQueryObject())
                .skip(pageable.getPageSize() * (pageable.getPageNumber() - 1))
                .limit(pageable.getPageSize());

Я получаю одно и то же исключение во всех случаях.

Примечание. Я знаю о других вопросах на StackOverflow, которые выглядят похожими на этот, но их цель немного отличается от того, чего я хочу достичь. Я хочу использовать объект запроса, чтобы сохранить динамический запрос.

Зависимости, которые я использую:

весна-загрузка-стартер-данные-mongodb | 2.3.3.ВЫПУСК

основной драйвер mongodb | 4.0.5

весна-данные-mongdb | 3.0.3.ВЫПУСК


person jayantS    schedule 14.09.2020    source источник


Ответы (2)


dis Вы создали Index On Collection вот так?

db.user.createIndex({ geolocation : "2dsphere" } );

Пожалуйста, запустите эту команду, а затем повторите попытку.

person Prakash Harvani    schedule 19.09.2020

Вы смешиваете клиент spring mongo и mongo. Например, для модели вы используете класс spring mongo - для запроса вы используете клиент mongo напрямую. Исправление будет заключаться в использовании любого из них.

Либо измените GeoJsonPoint на com.mongodb.client.model.geojson.Point, либо измените запрос на использование spring, например

Criteria criteria = Criteria.where(key).is(value);
Query query = Query.query(criteria).skip(num).limit(num);
List<ExcursionsModel> excursions = mongoTemplate.find(query, ExcursionsModel.class);

Рабочий пример (Spring Mongo Lib)

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.GeoNearOperation;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class So63886374Application {

    public static void main(String[] args) {
        SpringApplication.run(So63886374Application.class, args);
    }

    @Autowired
    private MongoTemplate mongoTemplate;

    public void queryData()  {
        //Distance from fort worth - 45 miles - output dallas - 200 miles - austin/dallas - 300 miles - austin/dallas/houston
        Criteria criteria = Criteria.where("geolocation").nearSphere(new GeoJsonPoint(-97.3327459, 32.753177)).maxDistance(200*1609.34);
        Query query = Query.query(criteria);
        List<ExcursionsModel> excursionsModels = mongoTemplate.find(query, ExcursionsModel.class);
        excursionsModels.forEach(es -> System.out.println(es.getGeolocation()));

        //Aggregation query to capture distance
        NearQuery nearQuery = NearQuery.near(-97.3327459, 32.753177, Metrics.MILES).maxDistance(200);
        GeoNearOperation geoNearOperation = Aggregation.geoNear(nearQuery, "distanceFromFW");
        Aggregation aggregation = Aggregation.newAggregation(geoNearOperation);
        List<ExcursionsModel> excursionsModelsAg = mongoTemplate.aggregate(aggregation, ExcursionsModel.class, ExcursionsModel.class).getMappedResults();
        excursionsModelsAg.forEach(es -> System.out.println(es.getDistanceFromFW()));
    }

    @Document
    public class ExcursionsModel {
        @GeoSpatialIndexed(name = "geoIndex", type = GeoSpatialIndexType.GEO_2DSPHERE)
        private GeoJsonPoint geolocation;
        private long distanceFromFW;
        public void setGeolocation(GeoJsonPoint geolocation) {
            this.geolocation = geolocation;
        }

        public GeoJsonPoint getGeolocation() {
            return geolocation;
        }

        public long getDistanceFromFW() {
            return distanceFromFW;
        }

        public void setDistanceFromFW(long distanceFromFW) {
            this.distanceFromFW = distanceFromFW;
        }
    }

    public void insertData()  {
        List<ExcursionsModel> excursionsModels = new ArrayList<>();
        mongoTemplate.indexOps(ExcursionsModel.class).ensureIndex(new GeospatialIndex("geolocation").named("geoIndex").typed(GeoSpatialIndexType.GEO_2DSPHERE));
        ExcursionsModel austin = new ExcursionsModel();
        austin.setGeolocation(new GeoJsonPoint( -97.7436995, 30.2711286));
        ExcursionsModel houston = new ExcursionsModel();
        houston.setGeolocation(new GeoJsonPoint(-95.3676974, 29.7589382));
        ExcursionsModel dallas = new ExcursionsModel();
        dallas.setGeolocation(new GeoJsonPoint(-96.7968559, 32.7762719));
        excursionsModels.add(austin);
        excursionsModels.add(houston);
        excursionsModels.add(dallas);
        mongoTemplate.insert(excursionsModels, ExcursionsModel.class);
    }

    public void dropData() {
        mongoTemplate.dropCollection(ExcursionsModel.class);
    }

    @Bean
    public ApplicationRunner runner() {
        return args -> {
            dropData();
            insertData();
            queryData();
        };
    }

}

Выход

Point [x=-96.796856, y=32.776272] //Dallas
Point [x=-97.743700, y=30.271129] //Austin
31
173

Пример (клиент Mongo — использование кодека Pojo)

public void queryData()  {
    //Distance from fort worth - 45 miles - output dallas - 200 miles - austin/dallas - 300 miles - austin/dallas/houston
    MongoClient mongoClient = MongoClients.create();
    MongoDatabase database = mongoClient.getDatabase("test");
    Point refPoint = new Point(new Position(-97.3327459, 32.753177));
    Bson filters = Filters.nearSphere("geolocation", refPoint, 200*1609.34, 0.0);
    CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
                fromProviders(PojoCodecProvider.builder().register(ExcursionsModel.class).build()));
    FindIterable<ExcursionsModel> excursionsModels = database.getCollection("excursionsmodel", ExcursionsModel.class).withCodecRegistry(pojoCodecRegistry).find(filters);
        excursionsModels.forEach(es -> System.out.println(es.getGeolocation()));
    }

public static class ExcursionsModel {
    private Point geolocation;    
    public void setGeolocation(Point geolocation) {
        this.geolocation = geolocation;
    }
    public Point getGeolocation() {
        return geolocation;
    }
}

Выход

Point{coordinate=Position{values=[-96.7968559, 32.7762719]}}
Point{coordinate=Position{values=[-97.7436995, 30.2711286]}}
person s7vr    schedule 17.09.2020
comment
Я попытался изменить GeoJsonPoint на Point, но это тоже не помогло. Исключение, которое я получаю, такое же: org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class com.mongodb.client.model.geojson.Point Я думаю, что это может быть связано с тем, что кодеки не работают/несовместимы с данной стратегией. Я временно заставил его работать, используя BsonDocument и создав запрос по-старому. - person jayantS; 23.09.2020
comment
При дальнейшем размышлении это не сработает, поскольку вы используете Spring для построения запроса критериев. Не могли бы вы попробовать другую альтернативу? то есть использовать mongoTemplate.find напрямую вместо вызова find в клиентской коллекции mongo - person s7vr; 24.09.2020