Spring Data Elasticsearch迁移:GeoShapeQuery JSON格式异常问题
问题说明
从Spring Data Elasticsearch 4.x迁移至5.x(搭配spring-boot-starter-data-elasticsearch 3.0.0),移除rhlc后,用新的GeoShapeQuery替代原QueryBuilders.geoShapeQuery构建查询时,发现shape被序列化为字符串而非JSON对象,不符合Elasticsearch的预期格式。
旧代码(4.x版本)
GeoShapeQueryBuilder geoShape = QueryBuilders.geoShapeQuery(ConvertUtils.FULL_GEO,new Point(lon, lat)); geoShape.relation(ShapeRelation.CONTAINS); QueryBuilder bool = new BoolQueryBuilder().filter(geoShape).should(rankFeature);
新代码(5.x版本)
JsonData shape = JsonData.of("{\"type\": \"point\",\"coordinates\": [ 13.0, 53.0 ]}"); Query geoShape = GeoShapeQuery.of(f->f.field(ConvertUtils.FULL_GEO) .shape(s->s.relation(GeoShapeRelation.Contains).shape(shape)))._toQuery(); Query bool = BoolQuery.of(b->b .filter(geoShape) .should(rankFeature) )._toQuery();
实际生成的查询语句
Query: {"geo_shape":{"fullGeo":{"shape":"{"type": "point","coordinates": [ 13.0, 53.0 ]}","relation":"contains"}}}
预期的查询语句
Query: {"geo_shape":{"fullGeo":{"shape":{"type": "point","coordinates": [ 13.0, 53.0 ]},"relation":"contains"}}}
解决方案
问题根源在于JsonData.of(String)的使用方式:这个方法会把传入的JSON字符串当作普通字符串字面量处理,而非解析成结构化的JSON对象。可以通过两种方式修复:
方案1:直接使用Elasticsearch形状类(推荐)
直接构造Point实体对象传入,Spring Data Elasticsearch会自动将其序列化为正确的JSON对象格式:
Point point = new Point(13.0, 53.0); Query geoShape = GeoShapeQuery.of(f -> f.field(ConvertUtils.FULL_GEO) .shape(s -> s.relation(GeoShapeRelation.Contains).shape(point)))._toQuery();
方案2:将JSON字符串解析为结构化JsonData
如果必须基于JSON字符串构建,需要先把字符串解析为JsonNode再转为JsonData:
ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree("{\"type\": \"point\",\"coordinates\": [ 13.0, 53.0 ]}"); JsonData shape = JsonData.from(jsonNode); Query geoShape = GeoShapeQuery.of(f -> f.field(ConvertUtils.FULL_GEO) .shape(s -> s.relation(GeoShapeRelation.Contains).shape(shape)))._toQuery();
原理说明
Spring Data Elasticsearch 5.x的新客户端API中,JsonData.of(String)是做字符串包装,而JsonData.from(JsonNode)或直接传入形状实体类,会将内容处理为结构化JSON对象,这样生成的查询才能匹配Elasticsearch对geo_shape查询的格式要求。
内容的提问来源于stack exchange,提问作者Shalom Ohayon

