最近工作中使用了Spring Data Elasticsearch。发生它存在一个问题:
Document对应的POJO的属性跟es里面文档的字段名字不一样,这样Repository里面编写自定义的查询方法就会查询不出结果。
比如有个Person类,它有2个属性goodFace和goodAt。这2个属性在es的索引里对应的字段表为good_face和good_at:
1 | 1, shards = 1, type = "person", indexName = "person") (replicas = |
Repository中的自定义查询:
1 |
|
方法findByGoodFace是查询不出结果的,而findByName是ok的。
为什么findByGoodFace不行而findByName可以呢,来探究一下。
Person类的name属性跟ES中的字段名是一模一样的,而goodFace字段在ES中的字段是good_face(因为我们使用了SnakeCaseStrategy策略)。
所以产生这个问题的原因在于ES中文档的字段名跟POJO中的字段名不统一造成的。
但是我们使用PersonRepository的save方法保存文档的时候属性和字段是可以对上的。
那为什么使用repository的save方法能对应上文档和字段,而自定义的find方法却不行呢?
ES是使用jackson来完成POJO到json的映射关系的。
在Person类上使用@JsonNaming注解完成POJO和json的映射,我们使用了SnakeCaseStrategy策略,这个策略会把属性从驼峰方式改成小写带下划线的方式。
比如goodAt属性映射的时候就会变成good_at,good_face变成good_face,name变成name。
Spring Data Elasticsearch把对ES的操作封装成了一个ElasticsearchOperations接口。比如queryForObject、queryForPage、count、queryForList方法。
ElasticsearchOperations接口目前有一个实现类ElasticsearchTemplate。
ElasticsearchTemplate内部有个ResultsMapper属性,这个ResultsMapper目前只有一个实现类DefaultResultMapper,DefaultResultMapper内部使用DefaultEntityMapper完成映射。DefaultEntityMapper是个EntityMapper接口的实现类,它的定义如下:
1 | public interface EntityMapper { |
方法很明白:对象到json字符串的转换和json字符串倒对象的转换。
DefaultEntityMapper内部使用jackson的ObjectMapper完成。
自定义的Repository继承自ElasticsearchRepository,最后会使用代理映射成SimpleElasticsearchRepository。
SimpleElasticsearchRepository内部有个属性ElasticsearchOperations用于完成与ES的交互。
我们看下SimpleElasticsearchRepository的save方法:
1 |
|
save方法使用ResultsMapper完成了POJO到json的转换,所以save方法保存成功对应的文档数据:
1 | indexRequestBuilder.setSource(resultsMapper.getEntityMapper().mapToString(query.getObject())); |
自定义的findByGoodFace方法:
由于是Repository中的自定义方法,会被Spring Data通过代理进行构造,内部还是用了AOP,最终在QueryExecutorMethodInterceptor中并解析成ElasticsearchPartQuery这个RepositoryQuery接口的实现类,然后调用execute方法:
1 |
|
findByGoodFace方法是个集合查询,最终会调用ElasticsearchOperations的queryForList方法:
1 |
|
自定义的方法使用ElasticsearchQueryCreator去创建CriteriaQuery,内部做一些词法的分析,有了CriteriaQuery之后,使用CriteriaQueryProcessor基于Criteria构造了QueryBuilder,最后使用QueryBuilder去做rest请求得到es的查询结果。这些过程中是没有用到ResultsMapper,而只是用反射得到POJO的属性,只有在得到查询结果后才会用ResultsMapper去做映射。
如果出现了这种情况,解决方案目前有两种:
1.使用repository的search方法,参数可以是QueryBuilder或者SearchQuery
1 | personRepository.search( |
2.使用@Query注解
1 | "{\"bool\" : {\"must\" : {\"term\" : {\"good_face\" : \"?0\"}}}}") ( |
暂时发现这两种解决方法,不知还有否更好的解决方案。