Elasticarch教程(13)Elasticarch⼯具类⽀持树形结构Elasticarch⼯具类⽀持树形结构
1. 前⾔
最近做的⼏个项⽬⽤ES作为数据库,⼀个项⽬⽤的开源的jest作为ES⼯具,感觉⽤的还可以,但是它好久不更新了。还有⼀个项⽬的⼯具类是⾃⼰写的,写的很粗糙,⽼⼤的意思要⽀持ES5.6和ES6.8这两个版本。后来我就⽤了ES5.6的Low Level Java API实现了常⽤CRUD ⽅法。后来体验了下Spring Data Elasticarch,感觉这个框架体验极好,API⾮常丰富,Spring出品的果然⽜。还有因为我常⽤Spring Data JPA,所以上⼿有很熟悉的感觉。
因为我技术⽐较菜,⼏年Java 开发⼯作中,也就CRUD,所以看Spring Data ES的源码很吃⼒,反正看不懂。所以想⾃⼰再写个简单的ES ⼯具类,全当熟悉下ES的Java API,和优雅的Spring Data ES⽐,相差⼗万⼋千⾥。
2. ⽬标
恰巧我⾃⼰写的第⼀个Java功能是⼀个ORM⼯具类,就是根据实体类,产⽣CRUD⽅法,所以对Java的泛型和反射还有⼀点点印象,所以写这个ES的ORM⼯具⼜有了当年熟悉的味道。先定好这次的⼏个⽬标:
2.1 ⽬标:基于实体类的CRUD
看了⽹上那么多JPA和Mybatis哪个好的⽂章,我感觉这些争吵毫⽆意义,适合⾃⼰的就是好的。我个⼈喜欢JPA的那种⾯向对象的调调,它也提供了⼿写SQL查询功能。所以我可能要实现的是如下风格的接⼝:
T sava(T t);
T findById(String id);
long count(QueryBuilder queryBuilder);
2.2 ⽬标:⽀持查询ES中树形结构数据
这⾥我说的ES中树形结构,它不是ES⾃带的⽗⼦⽂档,我感觉⽤ES的parent语法挺难⽤,也许是我太菜,不太会⽤ES的⽗⼦⽂档。
这⾥我说的ES的树形结构参考我上⼀篇博客
这⾥顺便提⼀下,树形结构最好⼀个节点只有⼀个⽗亲节点,⼀个节点多⽗亲的情况在⼯作中确实会遇到,但是那个坑很多,维护起来很⿇烦。所以对我个⼈⽽⾔,拒绝多⽗亲的树形结构。
3. 问题和解决
3.1 问题:获取泛型T的class,避免显⽰传⼊class
泛型T已经传来了,获取T的class,会让代码更加优雅。
不然你看下⽹上别⼈的代码,还得传⼀个clazz,是不是特别让⼈不爽。
T getById(M id, Class<T> clazz)
boolean exists(M id, Class<T> clazz)
long count(QueryBuilder queryBuilder, Class<T> clazz);
List<T>archMore(QueryBuilder queryBuilder,int limitSize, Class<T> clazz);
3.1 解决:抄Spring Data的作业
Spring Data ES⾥有段代码,不明觉厉。虽然我看不懂,但⼤概理解为⼦类(AbstractElasticarchRepository<T, ID>)实现接⼝(ElasticarchRepository<T, ID>),在⼦类中获取⽗亲的T的类型,这段我也就抄作业抄⼀半。
private ParameterizedType resolveReturnedClassFromGenericType(Class<?> clazz){
Object genericSuperclass = GenericSuperclass();
if(genericSuperclass instanceof ParameterizedType){
ParameterizedType parameterizedType =(ParameterizedType) genericSuperclass; Type rawtype = RawType();
if(SimpleElasticarchRepository.class.equals(rawtype)){
return parameterizedType;
}
}
return Superclass());
}
3.2 问题:树形结构如何设计
对于⼀个树形结构数据,我们常⽤到如下场景:
根据Id,获取其直接⼉⼦节点
根据Id,获取其所有⼦孙节点,例如⼦孙节点总个数
根据Id,获取其所有祖先节点
节点变更⽗亲,修改该节点所以⼦孙节点的path信息
删除⼀个节点,判断其下是否有⼦孙,有则不允许删除
3.2 解决:利⽤ES的nested类型,记录祖先节点ID四六级多少分及格线
参考,这⾥我给下ES的mapping和例⼦数据
PUT/pigg_tree/_mapping/_doc
{
"properties":{
泥泞的意思是什么"id":{
"type":"keyword"
},
"level":{
"type":"keyword"
},
"name":{
"type":"keyword"
},
"parentId":{
"type":"keyword"
},
"path":{
"type":"nested",
"properties":{
"id":{
etiquette"type":"keyword"
},
"level":{
"type":"keyword"
}
}
校泵
}
}
}
{
"_index":"pigg_tree",
"_type":"_doc",
"_id":"5ebdf2a8551fa08956079179",
"_score": null,
"_source":{
"parentId":"5ebdf263551fd81d52158964",
"level":3,重庆补习班
"path":[
{
"level":1,
"id":"5ebdf241551f9ae2328fa452"
2020年英语退出高考
},
{
"level":2,
"id":"5ebdf263551fd81d52158964"
}
],
"id":"5ebdf2a8551fa08956079179",
"name":"夏夏夏"
},
"sort":[
"夏夏夏",
"3"
]
}
4. 代码结构设计
4.1 普通结构接⼝-EsRepository
@NoRepositoryBean
public interface EsRepository<T>{
T save(T t);
T saveWithoutRefresh(T t);
Iterable<T>saveAll(Iterable<T> entities);
boolean deleteById(String id);
void deleteByQuery(QueryBuilder query);
boolean updateById(String id, Map<String, Object> doc);
void updateAllById(Iterable<String> ids, Map<String, Object> doc);
void updateByQuery(QueryBuilder query, Script script);
boolean existsById(String id);
2016欧洲杯决赛时间
Optional<T>findById(String id);
Optional<T>findById(String id, SourceFilter sourceFilter);
List<T>findAllById(Iterable<String> ids);
List<T>findAllById(Iterable<String> ids, SourceFilter sourceFilter);
List<T>findByQuery(QueryBuilder query);
List<T>findByQuery(QueryBuilder query, SourceFilter sourceFilter);
List<T>findByQuery(SearchQuery archQuery);
PageInfo<T>pageQuery(SearchQuery archQuery);
Long count(QueryBuilder query);
Map<String, Long>countGroupBy(String field, QueryBuilder query, Integer resultSize);
Class<T>getEntityClass();
}
4.2 树形结构接⼝-EsTreeRepository
@NoRepositoryBean
public interface EsTreeRepository<T extends TreeNode>extends EsRepository<T>{
T saveNode(T t);
Iterable<T>saveAllNodeOfParent(String parentId, Iterable<T> entities);
boolean deleteNodeById(String id);
List<T>findChildrenByParentId(String parentId,boolean onlyNextLevel, SearchQuery archQuery);
short怎么读List<T>findForefathersById(String id, SourceFilter sourceFilter);
Long countByParentId(String parentId,boolean onlyNextLevel, QueryBuilder query);
Map<String, Long>countByParentId(List<String> parentIds,boolean onlyNextLevel, QueryBuilder query); }
4.3 普通结构抽象类-AbstractEsRepository
剑桥国际public abstract class AbstractEsRepository<T> implements EsRepository<T>{
....
省略实现⽅法
....
}
4.4 树形结构抽象类-AbstractEsTreeRepository
@Component
public class AbstractEsTreeRepository<T extends TreeNode> extends AbstractEsRepository<T> implements EsTreeRepository<T>{
....
省略实现⽅法
....
}
4.5 树形结构基类-TreeNode
@Data
public class TreeNode {
@EsNodeParentId
private String parentId;
@EsNodeLevel
private int level;
@EsPath
private List<ParentNode> path;
}
4.6 测试对象实体类-TestTreeEntity
注意下⾯的@ToString(callSuper=true),因为我⽤了@Data注解,在反序列化时发现得到的对象没有
⽗类TreeNode的属性,经过排查发现是lombok默认重写了toString()⽅法,所以这样要加@ToString(callSuper=true),或者你就不要⽤lombok。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper=true)
@EsDocument(indexName ="pigg_tree", type ="_doc")
public class TestTreeEntity extends TreeNode {
@EsId
besides的用法private String id;
private String name;
}
5. 实现⽅法
因为代码实在太多了,不可能全部贴博客了,列举⼏个感觉⽐较重要的实现⽅法。
5.1 saveAll