嵌套类型
# 前言
在MySQL以及其它关系型数据库中,表与表之间相互关联可以用JOIN来实现,但ES中并没有JOIN,所以想要处理这种关联关系就需要了解嵌套类型父子类型和大宽表.
ES底层是Lucene,由于Lucene实际上是不支持嵌套类型的,所有文档都是以扁平的结构存储在Lucene中,ES对嵌套文档的支持,实际上也是采取了一种投机取巧的方式实现的.
嵌套的文档均以独立的文档存入,然后添加关联关系,这就会导致,一条嵌套类型的文档,底层实际上存储了N条数据,而且更新时会株连九族式更新,导致效率低下(如果您的业务场景是写多于读,那么建议您采用父子类型,如果读多于写,则用此嵌套类型).
对于嵌套类型, 我们并不建议您使用,除非万不得已,因为引入嵌套类型后,您后续的CRUD都会变得非常复杂,如果有嵌套+聚合的需求,其编码复杂度会让你怀疑人生.
ES本身更适合"大宽表"模式,不要带着传统关系型数据库那种思维方式去使用ES,我们完全可以通过把多张表中的字段和内容合并到一张表(一个索引)中,来完成期望功能,尽可能规避嵌套类型的使用,不仅效率高,功能也更强大.
当然存在即合理,也确实有个别场景下,不可避免的会用到嵌套类型,作为全球首屈一指的ES-ORM框架,我们对此也提供了支持,用户可以不用,但我们不能没有!
# 嵌套类型创建索引
- 自动挡模式:
按照下述配置,配置完成后直接启动项目,框架自动完成索引创建/更新
public class Document{
// 省略其它字段...
/**
* 嵌套类型
*/
@IndexField(fieldType = FieldType.NESTED, nestedClass = User.class)
private List<User> users;
}
2
3
4
5
6
7
8
注意
务必像上面示例一样指定类型为fieldType=NESTED及其nestedClass,否则会导致框架无法正常运行
- 手动挡模式
方式一:按照自动挡模式,配置好注解,然后直接调用一键生成API生成索引
documentMapper.createIndex();
方式二:纯手工打造,所有字段自己安排一遍,不推荐,麻烦得很
LambdaEsIndexWrapper<Document> wrapper = new LambdaEsIndexWrapper<>();
// 省略其它代码
wrapper.mapping(Document::getUsers, FieldType.NESTED);
2
3
注意
在手动挡模式下,除了要通过注解@TableField指定nestedClass外,还需要通过wrapper指定该嵌套字段,然后完成索引创建/更新
# 嵌套类型 CRUD
nested(String path, Consumer<Param> consumer);
nested(String path, Consumer<Param> consumer, ScoreMode scoreMode);
nested(boolean condition, String path, Consumer<Param> consumer);
nested(boolean condition, String path, Consumer<Param> consumer, ScoreMode scoreMode);
2
3
4
提示
其中path为当前查询对象字段名,例如我在Document对象中嵌套了User数组,字段名为users,又在User对象中嵌套了Faq数组,当我需要去查询User中满足 某些指定条件的数据时,传入的path就为字符串"users" 当我需要查询Faq中满足某些指定条件的数据时,则传入的path为字符串"users.faq" 另外字段的获取方式如果要采取Lambda方式获取,可以使用FieldUtils.val(R column)工具类获取.
其中增删改与非嵌套类型使用无差异,这里不赘述
查询示例:
@Test
public void testNestedMatch() {
// 嵌套查询 查询年龄等于18或8,且密码等于123的数据
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.nested("users", w ->
w.in(FieldUtils.val(User::getAge), 18, 8)
.eq(FieldUtils.val(User::getPassword), "123"));
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
// 嵌套查询 查询年龄满足18或者问题名称匹配'size也18吗'的全部数据
LambdaEsQueryWrapper<Document> wrapper2 = new LambdaEsQueryWrapper<>();
wrapper2.nested("users", w -> w.in("age", 18))
.or()
.nested("users.faqs", w -> w.match("faq_name", "size也18吗"));
List<Document> documents2 = documentMapper.selectList(wrapper2);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
相关demo可参考源码的test模块->test目录->nested包