父-子关系文档
父-子关系文档 在实质上类似于 nested model :允许将一个对象实体和另外一个对象实体关联起来。 而这两种类型的主要区别是:在 nested objects 文档中,所有对象都是在同一个文档中,而在父-子关系文档中,父对象和子对象都是完全独立的文档。
父-子关系的主要作用是允许把一个 type 的文档和另外一个 type 的文档关联起来,构成一对多的关系:一个父文档可以对应多个子文档 。与 nested objects 相比,父-子关系的主要优势有:
更新父文档时,不会重新索引子文档。
创建,修改或删除子文档时,不会影响父文档或其他子文档。这一点在这种场景下尤其有用:子文档数量较多,并且子文档创建和修改的频率高时。
子文档可以作为搜索结果独立返回。
Elasticsearch 维护了一个父文档和子文档的映射关系,得益于这个映射,父-子文档关联查询操作非常快。但是这个映射也对父-子文档关系有个限制条件:父文档和其所有子文档,都必须要存储在同一个分片中。
我使用的是es5.6,es6.0版本以上一个索引对应一个type
1.在es中查询父子级关系
创建父子级关系
PUT user_test { "mappings": { "dept": { "properties": { "dname":{ "type": "text" } } }, "user":{ "_parent": { "type": "dept" }, "properties": { "deptId":{ "type": "text" }, "username":{ "type": "text" }, "age":{ "type": "integer" } } } } }
创建了一个公司的索引 包括部门和用户两个类型
新增一个父级数据
POST company/dept/2 { "dname":"开发部" }
新增一个子级数据
POST company/user?parent=2 { "uid":"1", "uname":"王五", "age":10 }
以子级查
GET company/user/_search { "query": { "has_parent": { "parent_type": "dept", "query": { "term": { "dname":"开" } },"inner_hits":{} } } }
以父级查
GET company/dept/_search { "query": { "has_child": { "type": "user", "query": { "match": { "uname":"里斯" } },"inner_hits":{} } } }
用inner_hits则可以把父子文档同时返回——既返回,不加inner_hits只返回一个type里的数据。inner_hits默认只查询3条数据,可以自定义设置from 和size。
如果父子级同时有查询条件,用bool作为复合查询
GET company/dept/_search { "query": { "bool": { "must": [ { "term": { "dname": { "value": "开" } } },{ "has_child": { "type": "user", "query": { "match": { "uname":"张" } },"inner_hits":{} } } ] } } }
2.java项目中查询
实体类
1.部门
@Document(indexName = "user_test",type = "dept") public class Department { @Id private String id; private String dname; }
2.用户
@Document(indexName = "user_test",type = "user") public class User { @Id private String id; @Parent(type = "dept") private String deptId; private String uname; private Integer age; }
使用ElasticsearchTemplate
1查询父级数据
QueryBuilder qb=JoinQueryBuilders.hasChildQuery("user",QueryBuilders.matchAllQuery(),ScoreMode.None); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices("user_test") .withQuery(qb) .build(); List<Department> depts= elasticsearchTemplate.queryForList(searchQuery,Department.class);
ScoreMode:评分模式min
,max
,sum
,avg
或none
2.查询子级数据
QueryBuilder qb=JoinQueryBuilders.hasParentQuery("dept",QueryBuilders.matchQuery("dname","开"),false); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices("user_test") .withQuery(qb) .build(); List<User> users= elasticsearchTemplate.queryForList(searchQuery,User.class);
评分功能:这has_parent
也有得分支持。默认值是false
忽略父文档的分数
官方参考:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-has-parent-query.html
查询父子级全部数据
QueryBuilder qb = JoinQueryBuilders.hasChildQuery( "user", //要查询的子类型 QueryBuilders.matchQuery("uname.keyword","张三"), ScoreMode.None ).innerHit(new InnerHitBuilder()); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices("user_test") .withQuery(qb) .build(); List<DeptVO> depts= elasticsearchTemplate.query(searchQuery, searchResponse -> { SearchHits hits = searchResponse.getHits(); List<DeptVO> list = new ArrayList<>(); Arrays.stream(hits.getHits()).forEach(h -> { Map<String, Object> source = h.getSource(); SearchHits innerHitsMap=h.getInnerHits().get("user");//获取子级数据 List<User> user1s=Arrays.stream(innerHitsMap.getHits()).map(innerH -> { Map<String, Object> innerSource = innerH.getSource(); return new User(innerSource.get("uname").toString(),Integer.valueOf(innerSource.get("age").toString())); }).collect(Collectors.toList()); list.add(new DeptVO(source.get("dname").toString(),user1s)); }); return list; });
JoinQueryBuilders.hasChildQuery().innerHit(new InnerHitBuilder())的.innerHit(new InnerHitBuilder())与es查询一样