v2.0.0-beta6

1.新增父子类型对一父多子以及一父多子多孙场景的索引自动化支持
2.增加对父子类型所有查询功能支持
3.所有CRUD方法支持方法粒度的自定义路由功能
4.提供全新自定义注解@Settings,支持便捷预设与灵活自定义功能,可支持ES索引中所有Settings的编辑处理
5.javadoc及注释和部分代码细节优化
6.其它优化持续更新中...
This commit is contained in:
xpc 2024-03-17 21:50:14 +08:00
parent 46a0081fad
commit 53b906da78
20 changed files with 359 additions and 101 deletions

View File

@ -5,6 +5,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static org.dromara.easyes.annotation.rely.AnnotationConstants.DEFAULT_JOIN_FIELD_NAME;
/**
* 父子类型
* <p>
@ -13,8 +15,24 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Join {
String joinField() default "joinField";
/**
* join字段在es中的名字
*
* @return 索引中的join字段名称 默认为joinField
*/
String joinField() default DEFAULT_JOIN_FIELD_NAME;
/**
* 根节点别名 不指定则默认使用加了当前注解的根类的名称小写作为根节点别名(推荐)
*
* @return 根节点别名
*/
String rootAlias() default "";
Child[] children() default {};
/**
* 非根节点
*
* @return 非根节点列表
*/
Node[] nodes() default {};
}

View File

@ -12,9 +12,9 @@ import java.lang.annotation.Target;
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
public @interface Child {
public @interface Node {
/**
* 父文档别名 非必填,默认值为当前类名小写
* 父文档别名 非必填,不指定时默认值为parentClass类名小写(推荐)
*
* @return 父文档别名
*/
@ -28,9 +28,9 @@ public @interface Child {
Class<?> parentClass();
/**
* 子文档别名列表
* 子文档别名列表,不指定则为子文档类名小写列表(推荐) 若要自定义必须与childClasses数量和顺序一致
*
* @return 子文档别名列表 非必填,默认值为子文档类名小写
* @return 子文档别名列表 非必填,默认值为子文档类名小写列表
*/
String[] childAliases() default {};

View File

@ -34,4 +34,8 @@ public interface AnnotationConstants {
* 默认索引别名
*/
String DEFAULT_ALIAS = "ee_default_alias";
/**
* 默认join字段名称
*/
String DEFAULT_JOIN_FIELD_NAME = "joinField";
}

View File

@ -6,6 +6,7 @@ import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
@ -13,8 +14,8 @@ import org.elasticsearch.search.sort.SortOrder;
import java.io.Serializable;
import java.util.*;
import static org.dromara.easyes.common.constants.BaseEsConstants.DEFAULT_BOOST;
import static java.util.stream.Collectors.toList;
import static org.dromara.easyes.common.constants.BaseEsConstants.DEFAULT_BOOST;
/**
* 高阶语法相关
@ -1704,4 +1705,45 @@ public interface Func<Children, R> extends Serializable {
* @return wrapper
*/
Children mix(boolean condition, QueryBuilder queryBuilder);
/**
* 聚合桶排序
*
* @param bucketOrder 排序规则
* @return wrapper
*/
default Children bucketOrder(BucketOrder bucketOrder) {
return bucketOrder(true, bucketOrder);
}
/**
* 聚合桶排序
*
* @param condition 条件
* @param bucketOrder 桶排序规则
* @return wrapper
*/
default Children bucketOrder(boolean condition, BucketOrder bucketOrder) {
return bucketOrder(condition, Arrays.asList(bucketOrder));
}
/**
* 聚合桶排序
*
* @param bucketOrders 排序规则列表
* @return wrapper
*/
default Children bucketOrder(List<BucketOrder> bucketOrders) {
return bucketOrder(true, bucketOrders);
}
/**
* 聚合桶排序
*
* @param condition 条件
* @param bucketOrders 排序规则列表
* @return wrapper
*/
Children bucketOrder(boolean condition, List<BucketOrder> bucketOrders);
}

View File

@ -214,6 +214,15 @@ public interface Nested<Param, Children> extends Serializable {
*/
Children hasChild(boolean condition, String type, Consumer<Param> consumer, ScoreMode scoreMode);
/**
* 父子类型-根据子查父匹配 返回子文档 无需指定父,由框架根据@Join注解自行推断其父
*
* @param consumer 嵌套条件函数
* @return wrapper
*/
default Children hasParent(Consumer<Param> consumer) {
return hasParent(true, null, consumer);
}
/**
* 父子类型-根据子查父匹配 返回子文档

View File

@ -13,6 +13,7 @@ import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
@ -654,6 +655,13 @@ public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainW
return typedThis;
}
@Override
public Children bucketOrder(boolean condition, List<BucketOrder> bucketOrders) {
getWrapper().bucketOrder(condition, bucketOrders);
return typedThis;
}
@Override
public Children select(String... columns) {
getWrapper().select(columns);

View File

@ -17,6 +17,7 @@ import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
@ -567,6 +568,14 @@ public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T,
return addParam(condition, queryBuilder);
}
@Override
public Children bucketOrder(boolean condition, List<BucketOrder> bucketOrders) {
if (condition) {
this.bucketOrders = bucketOrders;
}
return typedThis;
}
@Override
public Children select(String... columns) {
this.include = columns;

View File

@ -224,11 +224,21 @@ public interface BaseEsMapper<T> {
/**
* 插入一条记录 可指定路由
*
* @param entity 插入的数据对象
* @param routing 路由
* @param entity 插入的数据对象
* @return 成功条数
*/
Integer insert(T entity, String routing);
Integer insert(String routing, T entity);
/**
* 父子类型 插入一条记录 可指定路由, 父id
*
* @param routing 路由
* @param parentId 父id
* @param entity 插入的数据对象
* @return 成功条数
*/
Integer insert(String routing, String parentId, T entity);
/**
* 插入一条记录,可指定多索引插入
@ -240,7 +250,7 @@ public interface BaseEsMapper<T> {
Integer insert(T entity, String... indexNames);
/**
* 插入一条记录,可指定路由及多索引插入
* 插入数据,可指定路由及多索引插入
*
* @param routing 路由
* @param entity 插入的数据对象
@ -249,6 +259,16 @@ public interface BaseEsMapper<T> {
*/
Integer insert(String routing, T entity, String... indexNames);
/**
* 父子类型 插入数据,可指定路由,父id及多索引插入
*
* @param routing 路由
* @param parentId 父id
* @param entity 插入的数据对象
* @param indexNames 指定插入的索引名数组
* @return 总成功条数
*/
Integer insert(String routing, String parentId, T entity, String... indexNames);
/**
* 批量插入
@ -261,11 +281,21 @@ public interface BaseEsMapper<T> {
/**
* 批量插入 可指定路由
*
* @param entityList 插入的数据对象列表
* @param routing 路由
* @param entityList 插入的数据对象列表
* @return 总成功条数
*/
Integer insertBatch(Collection<T> entityList, String routing);
Integer insertBatch(String routing, Collection<T> entityList);
/**
* 父子类型 批量插入 可指定路由, 父id
*
* @param routing 路由
* @param parentId 父id
* @param entityList 插入的数据对象列表
* @return 总成功条数
*/
Integer insertBatch(String routing, String parentId, Collection<T> entityList);
/**
* 批量插入 可指定多索引
@ -286,6 +316,17 @@ public interface BaseEsMapper<T> {
*/
Integer insertBatch(String routing, Collection<T> entityList, String... indexNames);
/**
* 父子类型 批量插入 可指定路由,父id及多索引
*
* @param routing 路由
* @param parentId 父id
* @param entityList 插入的数据对象列表
* @param indexNames 指定插入的索引名数组
* @return 总成功条数
*/
Integer insertBatch(String routing, String parentId, Collection<T> entityList, String... indexNames);
/**
* 根据 ID 删除
*
@ -297,11 +338,11 @@ public interface BaseEsMapper<T> {
/**
* 根据 ID 删除 可指定路由
*
* @param id 主键
* @param routing 路由
* @param id 主键
* @return 成功条数
*/
Integer deleteById(Serializable id, String routing);
Integer deleteById(String routing, Serializable id);
/**
* 根据 ID 删除 可指定多索引
@ -334,11 +375,11 @@ public interface BaseEsMapper<T> {
/**
* 删除根据ID 批量删除可指定路由
*
* @param idList 主键列表
* @param routing 路由
* @param idList 主键列表
* @return 总成功条数
*/
Integer deleteBatchIds(Collection<? extends Serializable> idList, String routing);
Integer deleteBatchIds(String routing, Collection<? extends Serializable> idList);
/**
* 删除根据ID 批量删除
@ -378,11 +419,11 @@ public interface BaseEsMapper<T> {
/**
* 根据 ID 更新 可指定路由
*
* @param entity 更新对象
* @param routing 路由
* @param entity 更新对象
* @return 总成功条数
*/
Integer updateById(T entity, String routing);
Integer updateById(String routing, T entity);
/**
* 根据 ID 更新 可指定多索引
@ -414,11 +455,11 @@ public interface BaseEsMapper<T> {
/**
* 根据ID 批量更新 可指定路由
*
* @param entityList 更新对象列表
* @param routing 路由
* @param entityList 更新对象列表
* @return 总成功条数
*/
Integer updateBatchByIds(Collection<T> entityList, String routing);
Integer updateBatchByIds(String routing, Collection<T> entityList);
/**
* 根据ID 批量更新 可指定多索引
@ -459,11 +500,11 @@ public interface BaseEsMapper<T> {
/**
* 根据 ID 查询 可指定路由
*
* @param id 主键
* @param routing 路由
* @param id 主键
* @return 指定的返回对象
*/
T selectById(Serializable id, String routing);
T selectById(String routing, Serializable id);
/**
* 根据 ID 查询 可指定多索引
@ -493,13 +534,13 @@ public interface BaseEsMapper<T> {
List<T> selectBatchIds(Collection<? extends Serializable> idList);
/**
* 查询根据ID 批量查询
* 查询根据ID 批量查询 可指定路由
*
* @param idList 主键列表
* @param routing 路由
* @param idList 主键列表
* @return 指定的返回对象列表
*/
List<T> selectBatchIds(Collection<? extends Serializable> idList, String routing);
List<T> selectBatchIds(String routing, Collection<? extends Serializable> idList);
/**
* 查询根据ID 批量查询 可指定多索引

View File

@ -330,48 +330,66 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
@Override
public Integer insert(T entity) {
Assert.notNull(entity, "insert entity must not be null");
return insert(null, entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
return insert(null, null, entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@Override
public Integer insert(T entity, String routing) {
Assert.notNull(entity, "insert entity must not be null");
return insert(routing, entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
public Integer insert(String routing, T entity) {
return insert(routing, null, entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@Override
public Integer insert(String routing, String parentId, T entity) {
return insert(routing, parentId, entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@Override
public Integer insert(T entity, String... indexNames) {
return insert(null, entity, indexNames);
return insert(null, null, entity, indexNames);
}
@Override
public Integer insert(String routing, T entity, String... indexNames) {
return insert(routing, null, entity, indexNames);
}
@Override
public Integer insert(String routing, String parentId, T entity, String... indexNames) {
Assert.notNull(entity, "insert entity must not be null");
// 执行插入
return Arrays.stream(getIndexNames(indexNames))
.mapToInt(indexName -> doInsert(entity, routing, indexName))
.mapToInt(indexName -> doInsert(entity, routing, parentId, indexName))
.sum();
}
@Override
public Integer insertBatch(Collection<T> entityList) {
return insertBatch(null, entityList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
return insertBatch(null, null, entityList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@Override
public Integer insertBatch(Collection<T> entityList, String routing) {
public Integer insertBatch(String routing, Collection<T> entityList) {
return insertBatch(routing, entityList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@Override
public Integer insertBatch(String routing, String parentId, Collection<T> entityList) {
return insertBatch(routing, parentId, entityList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@Override
public Integer insertBatch(Collection<T> entityList, String... indexNames) {
return insertBatch(null, entityList, indexNames);
return insertBatch(null, null, entityList, indexNames);
}
@Override
public Integer insertBatch(String routing, Collection<T> entityList, String... indexNames) {
return insertBatch(routing, null, entityList, indexNames);
}
@Override
public Integer insertBatch(String routing, String parentId, Collection<T> entityList, String... indexNames) {
// 老汉裤子都脱了 你告诉我没有数据 怎么*?
if (CollectionUtils.isEmpty(entityList)) {
return BaseEsConstants.ZERO;
@ -379,7 +397,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
// 在每条指定的索引上批量执行数据插入
return Arrays.stream(getIndexNames(indexNames))
.mapToInt(indexName -> doInsertBatch(entityList, routing, indexName))
.mapToInt(indexName -> doInsertBatch(entityList, routing, parentId, indexName))
.sum();
}
@ -389,7 +407,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
}
@Override
public Integer deleteById(Serializable id, String routing) {
public Integer deleteById(String routing, Serializable id) {
return deleteById(routing, id, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@ -411,7 +429,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
}
@Override
public Integer deleteBatchIds(Collection<? extends Serializable> idList, String routing) {
public Integer deleteBatchIds(String routing, Collection<? extends Serializable> idList) {
return deleteBatchIds(routing, idList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@ -457,7 +475,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
}
@Override
public Integer updateById(T entity, String routing) {
public Integer updateById(String routing, T entity) {
return updateById(routing, entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@ -485,7 +503,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
}
@Override
public Integer updateBatchByIds(Collection<T> entityList, String routing) {
public Integer updateBatchByIds(String routing, Collection<T> entityList) {
return updateBatchByIds(routing, entityList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@ -524,7 +542,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
}
@Override
public T selectById(Serializable id, String routing) {
public T selectById(String routing, Serializable id) {
return selectById(routing, id, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@ -553,7 +571,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
}
@Override
public List<T> selectBatchIds(Collection<? extends Serializable> idList, String routing) {
public List<T> selectBatchIds(String routing, Collection<? extends Serializable> idList) {
return selectBatchIds(routing, idList, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
}
@ -686,12 +704,14 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
*
* @param entity 插入对象
* @param routing 路由
* @param parentId 父id
* @param indexName 索引名
* @return 成功条数
*/
private Integer doInsert(T entity, String routing, String indexName) {
private Integer doInsert(T entity, String routing, String parentId, String indexName) {
// 构建请求入参
IndexRequest indexRequest = buildIndexRequest(entity, routing, indexName);
IndexRequest indexRequest = buildIndexRequest(entity, routing, parentId, indexName);
Optional.ofNullable(routing).ifPresent(indexRequest::routing);
indexRequest.setRefreshPolicy(getRefreshPolicy());
@ -716,16 +736,17 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
*
* @param entityList 数据列表
* @param routing 路由
* @param parentId 父id
* @param indexName 索引名
* @return 总成功条数
*/
private Integer doInsertBatch(Collection<T> entityList, String routing, String indexName) {
private Integer doInsertBatch(Collection<T> entityList, String routing, String parentId, String indexName) {
// 构建批量请求参数
BulkRequest bulkRequest = new BulkRequest();
Optional.ofNullable(routing).ifPresent(bulkRequest::routing);
bulkRequest.setRefreshPolicy(getRefreshPolicy());
entityList.forEach(entity -> {
IndexRequest indexRequest = buildIndexRequest(entity, routing, indexName);
IndexRequest indexRequest = buildIndexRequest(entity, routing, parentId, indexName);
bulkRequest.add(indexRequest);
});
@ -1033,10 +1054,11 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
*
* @param entity 实体
* @param routing 路由
* @param parentId 父id
* @param indexName 索引名
* @return es请求参数
*/
private IndexRequest buildIndexRequest(T entity, String routing, String indexName) {
private IndexRequest buildIndexRequest(T entity, String routing, String parentId, String indexName) {
IndexRequest indexRequest = new IndexRequest();
// id预处理,除下述情况,其它情况使用es默认的id
@ -1058,7 +1080,7 @@ public class BaseEsMapperImpl<T> implements BaseEsMapper<T> {
joinField.setName(entityInfo.getJoinAlias());
if (entityInfo.isChild()) {
// 子类型,需要追加父
joinField.setParent(routing);
joinField.setParent(parentId);
}
jsonObject.put(entityInfo.getJoinFieldName(), joinField);
jsonData = jsonObject.toJSONString();

View File

@ -4,6 +4,7 @@ package org.dromara.easyes.core.core;
import lombok.SneakyThrows;
import org.dromara.easyes.core.biz.*;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.util.LinkedList;
@ -78,6 +79,11 @@ public abstract class Wrapper<T> implements Cloneable {
*/
protected List<AggregationParam> aggregationParamList;
/**
* 聚合桶排序规则列表
*/
List<BucketOrder> bucketOrders;
/**
* 排序参数列表
*/

View File

@ -21,6 +21,7 @@ import org.elasticsearch.join.query.HasParentQueryBuilder;
import org.elasticsearch.join.query.ParentIdQueryBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
@ -246,7 +247,9 @@ public class WrapperProcessor {
setBool(bool, nestedQueryBuilder, param.getPrevQueryType());
break;
case HAS_PARENT:
realField = getRealField(param.getColumn(), mappingColumnMap);
// 如果用户没指定type框架可根据entityInfo上下文自行推断出其父type
String column = Optional.ofNullable(param.getColumn()).orElse(entityInfo.getParentJoinAlias());
realField = getRealField(column, mappingColumnMap);
queryBuilder = getBool(children, QueryBuilders.boolQuery(), entityInfo, param.getColumn());
HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder(realField, queryBuilder, (boolean) param.getVal());
setBool(bool, hasParentQueryBuilder, param.getPrevQueryType());
@ -555,7 +558,7 @@ public class WrapperProcessor {
AggregationBuilder cursor = null;
for (AggregationParam aggParam : aggregationParamList) {
String realField = getRealField(aggParam.getField(), mappingColumnMap);
AggregationBuilder builder = getRealAggregationBuilder(aggParam.getAggregationType(), aggParam.getName(), realField);
AggregationBuilder builder = getRealAggregationBuilder(aggParam.getAggregationType(), aggParam.getName(), realField, wrapper.size, wrapper.bucketOrders);
if (aggParam.isEnablePipeline()) {
// 管道聚合, 构造聚合树
if (root == null) {
@ -588,9 +591,10 @@ public class WrapperProcessor {
* @param aggType 聚合类型
* @param name 聚合返回桶的名称 保持原字段名称
* @param realField 原字段名称
* @param size 聚合桶大小
* @return 聚合建造者
*/
private static AggregationBuilder getRealAggregationBuilder(AggregationTypeEnum aggType, String name, String realField) {
private static AggregationBuilder getRealAggregationBuilder(AggregationTypeEnum aggType, String name, String realField, Integer size, List<BucketOrder> bucketOrders) {
AggregationBuilder aggregationBuilder;
// 解决同一个字段聚合多次如min(starNum), max(starNum) 字段名重复问题
name += aggType.getValue();
@ -608,7 +612,10 @@ public class WrapperProcessor {
aggregationBuilder = AggregationBuilders.sum(name).field(realField);
break;
case TERMS:
aggregationBuilder = AggregationBuilders.terms(name).field(realField);
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(name).field(realField);
Optional.ofNullable(size).ifPresent(termsAggregationBuilder::size);
Optional.ofNullable(bucketOrders).ifPresent(termsAggregationBuilder::order);
aggregationBuilder = termsAggregationBuilder;
break;
default:
throw new UnsupportedOperationException("不支持的聚合类型,参见AggregationTypeEnum");

View File

@ -107,7 +107,7 @@ public class EntityInfoHelper {
*/
private static void initJoin(Class<?> clazz, GlobalConfig globalConfig, EntityInfo entityInfo) {
Join join = clazz.getAnnotation(Join.class);
if (join == null || ArrayUtils.isEmpty(join.children())) {
if (join == null || ArrayUtils.isEmpty(join.nodes())) {
return;
}
boolean camelCase = globalConfig.getDbConfig().isMapUnderscoreToCamelCase();
@ -119,7 +119,7 @@ public class EntityInfoHelper {
entityInfo.setJoinAlias(underlineJoinAlias);
Map<String, List<String>> relationMap = entityInfo.getRelationMap();
Arrays.stream(join.children())
Arrays.stream(join.nodes())
.forEach(child -> {
String parentAlias = StringUtils.isBlank(child.parentAlias()) ? child.parentClass().getSimpleName().toLowerCase() : child.parentAlias();
String underlineParentAlias = camelToUnderline(parentAlias, camelCase);

View File

@ -2,10 +2,7 @@ package org.dromara.easyes.sample.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.easyes.annotation.HighLight;
import org.dromara.easyes.annotation.IndexField;
import org.dromara.easyes.annotation.IndexId;
import org.dromara.easyes.annotation.IndexName;
import org.dromara.easyes.annotation.*;
import org.dromara.easyes.annotation.rely.Analyzer;
import org.dromara.easyes.annotation.rely.FieldStrategy;
import org.dromara.easyes.annotation.rely.FieldType;
@ -18,7 +15,8 @@ import org.dromara.easyes.annotation.rely.IdType;
**/
@Data
@Accessors(chain = true)
@IndexName(value = "easyes_document", shardsNum = 3, replicasNum = 2, keepGlobalPrefix = true, maxResultWindow = 100)
@Settings(shardsNum = 3, replicasNum = 2, maxResultWindow = 1000)
@IndexName(value = "easyes_document", keepGlobalPrefix = true)
public class Document {
/**
* es中的唯一id,如果你想自定义es中的id为你提供的id,比如MySQL中的id,请将注解中的type指定为customize或直接在全局配置文件中指定,如此id便支持任意数据类型)

View File

@ -3,26 +3,25 @@ package org.dromara.easyes.test.entity;
import lombok.Data;
import org.dromara.easyes.annotation.IndexField;
import org.dromara.easyes.annotation.IndexName;
import org.dromara.easyes.annotation.rely.Analyzer;
import org.dromara.easyes.annotation.IndexId;
import org.dromara.easyes.annotation.rely.FieldType;
/**
* es 作者 数据模型 Document的子文档,Document是其父文档
* 作者 数据模型 Document的子文档,Document是其父文档
* <p>
* Copyright © 2021 xpc1024 All Rights Reserved
* Copyright © 2024 xpc1024 All Rights Reserved
**/
@Data
public class Author {
/**
* 作者id
*/
@IndexField(fieldType = FieldType.KEYWORD)
@IndexId
private String authorId;
/**
* 作者姓名
*/
@IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_SMART)
@IndexField(fieldType = FieldType.KEYWORD)
private String authorName;
}

View File

@ -3,25 +3,25 @@ package org.dromara.easyes.test.entity;
import lombok.Data;
import org.dromara.easyes.annotation.IndexField;
import org.dromara.easyes.annotation.IndexName;
import org.dromara.easyes.annotation.IndexId;
import org.dromara.easyes.annotation.rely.FieldType;
/**
* es 联系方式 数据模型 Author的子文档,Author是其父文档
* 联系方式 数据模型 Author的子文档,Author是其父文档,Document是其爷文档
* <p>
* Copyright © 2021 xpc1024 All Rights Reserved
* Copyright © 2024 xpc1024 All Rights Reserved
**/
@Data
public class Contact {
/**
* 手机号
* 联系人id
*/
@IndexField(fieldType = FieldType.KEYWORD)
private String phone;
@IndexId
private String contactId;
/**
* 邮箱
* 地址
*/
@IndexField(fieldType = FieldType.TEXT)
private String email;
private String address;
}

View File

@ -11,7 +11,14 @@ import java.math.BigDecimal;
import java.util.List;
/**
* es 数据模型
* es 数据模型 其中Join父子类型结构如下所示
* <pre>
* Document
* / \
* Comment Author
* \
* Contact
* </pre>
* <p>
* Copyright © 2021 xpc1024 All Rights Reserved
**/
@ -19,7 +26,7 @@ import java.util.List;
@Accessors(chain = true)
@Settings(shardsNum = 3, replicasNum = 2, settingsProvider = MySettingsProvider.class)
@IndexName(value = "easyes_document", keepGlobalPrefix = true, refreshPolicy = RefreshPolicy.IMMEDIATE)
@Join(children = {@Child(parentClass = Document.class, childClasses = {Author.class, Comment.class}), @Child(parentClass = Author.class, childClasses = Contact.class)})
@Join(nodes = {@Node(parentClass = Document.class, childClasses = {Author.class, Comment.class}), @Node(parentClass = Author.class, childClasses = Contact.class)})
public class Document {
/**
* es中的唯一id,字段名随便起,我这里演示用esId,你也可以用id(推荐),bizId等.

View File

@ -1,6 +1,7 @@
package org.dromara.easyes.test.mapper;
import org.dromara.easyes.annotation.EsDS;
import org.dromara.easyes.core.core.BaseEsMapper;
import org.dromara.easyes.test.entity.Author;
@ -9,5 +10,6 @@ import org.dromara.easyes.test.entity.Author;
* <p>
* Copyright © 2024 xpc1024 All Rights Reserved
**/
@EsDS("ds1")
public interface AuthorMapper extends BaseEsMapper<Author> {
}

View File

@ -1,6 +1,7 @@
package org.dromara.easyes.test.mapper;
import org.dromara.easyes.annotation.EsDS;
import org.dromara.easyes.core.core.BaseEsMapper;
import org.dromara.easyes.test.entity.Contact;
@ -9,5 +10,6 @@ import org.dromara.easyes.test.entity.Contact;
* <p>
* Copyright © 2024 xpc1024 All Rights Reserved
**/
@EsDS("ds1")
public interface ContactMapper extends BaseEsMapper<Contact> {
}

View File

@ -6,9 +6,13 @@ import org.dromara.easyes.core.conditions.update.LambdaEsUpdateWrapper;
import org.dromara.easyes.core.toolkit.EntityInfoHelper;
import org.dromara.easyes.core.toolkit.FieldUtils;
import org.dromara.easyes.test.TestEasyEsApplication;
import org.dromara.easyes.test.entity.Author;
import org.dromara.easyes.test.entity.Comment;
import org.dromara.easyes.test.entity.Contact;
import org.dromara.easyes.test.entity.Document;
import org.dromara.easyes.test.mapper.AuthorMapper;
import org.dromara.easyes.test.mapper.CommentMapper;
import org.dromara.easyes.test.mapper.ContactMapper;
import org.dromara.easyes.test.mapper.DocumentMapper;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.context.SpringBootTest;
@ -17,7 +21,16 @@ import javax.annotation.Resource;
import java.util.List;
/**
* 父子类型测试
* 父子类型测试 其结构如下所示,Document文档有子文档Author(作者)和Comment(评论),其中Author还有个子文档Contact(联系方式)
* 下述结构可参考加在Document上的自定义注解@Join和@Node来表达
* <pre>
* Document
* / \
* Comment Author
* \
* Contact
* </pre>
*
* <p>
* Copyright © 2022 xpc1024 All Rights Reserved
**/
@ -29,6 +42,16 @@ public class JoinTest {
private DocumentMapper documentMapper;
@Resource
private CommentMapper commentMapper;
@Resource
private AuthorMapper authorMapper;
@Resource
private ContactMapper contactMapper;
/**
* 固定路由,确保后续CRUD中所有父子文档均在统一分片上
*/
private final static String FIXED_ROUTING = "testRouting";
@Test
@Order(0)
@ -41,29 +64,59 @@ public class JoinTest {
@Test
@Order(1)
public void testInsert() throws InterruptedException {
// 测试新增父子文档,此处开启自动挡模式,父子类型索引已被自动处理
// 新新增父文档,然后再插入子文档
String parentId = "doc-1";
Document document = new Document();
// 此处id为自定义id,也可去掉注解@IndexId(type = IdType.CUSTOMIZE),让es自行生成id(推荐), insert完成后 es生成的id直接从document中取获取
document.setEsId(parentId);
document.setTitle("我是父文档的标题");
document.setContent("我是父文档的内容 father content ");
documentMapper.insert(document);
Document root = new Document();
root.setEsId(parentId);
root.setTitle("我是父文档的标题");
root.setContent("father doc");
documentMapper.insert(FIXED_ROUTING, root);
Thread.sleep(2000);
// 插入子文档
Comment comment = new Comment();
comment.setId("comment-1");
comment.setCommentContent("test1");
// 这里特别注意,子文档必须指定其路由为父亲的id,否则傻儿子找不到爹别怪我没提醒 (es语法如此,非框架限制)
commentMapper.insert(comment, parentId);
// 插入子文档1
Comment nodeA1 = new Comment();
nodeA1.setId("comment-1");
nodeA1.setCommentContent("test1");
// 这里特别注意,子文档必须指定其路由和父亲文档相同,否则傻儿子找不到爹别怪我没提醒 (es语法如此,非框架限制)
commentMapper.insert(FIXED_ROUTING, parentId, nodeA1);
// 插入子文档2
Comment comment1 = new Comment();
comment1.setId("comment-2");
comment1.setCommentContent("test2");
commentMapper.insert(comment1, parentId);
Comment nodeA2 = new Comment();
nodeA2.setId("comment-2");
nodeA2.setCommentContent("test2");
commentMapper.insert(FIXED_ROUTING, parentId, nodeA2);
// 插入子文档3
Author nodeB1 = new Author();
nodeB1.setAuthorId("author-1");
nodeB1.setAuthorName("tom");
authorMapper.insert(FIXED_ROUTING, parentId, nodeB1);
// 插入子文档4
Author nodeB2 = new Author();
nodeB2.setAuthorId("author-2");
nodeB2.setAuthorName("cat");
authorMapper.insert(FIXED_ROUTING, parentId, nodeB2);
Thread.sleep(2000);
// 插入孙子文档1(把孙子1挂在子文档3上)
Contact child1 = new Contact();
child1.setContactId("contact-1");
child1.setAddress("zhejiang province");
contactMapper.insert(FIXED_ROUTING, nodeB1.getAuthorId(), child1);
// 插入孙子文档2(把孙子2挂在子文档3上)
Contact child2 = new Contact();
child2.setContactId("contact-2");
child2.setAddress("hangzhou city");
contactMapper.insert(FIXED_ROUTING, nodeB1.getAuthorId(), child2);
// 插入孙子文档3(把孙子3挂在子文档4上)
Contact child3 = new Contact();
child3.setContactId("contact-3");
child3.setAddress("binjiang region");
contactMapper.insert(FIXED_ROUTING, nodeB2.getAuthorId(), child3);
// es写入数据有延迟 适当休眠 保证后续查询结果正确
Thread.sleep(2000);
@ -72,25 +125,49 @@ public class JoinTest {
@Test
@Order(2)
public void testSelect() {
// 温馨提示,下面wrapper中的type实际上就是JoinField字段注解@TableField中指定的parentName和childName,与原生语法是一致的
// 温馨提示,下面wrapper中的type实际上就是索引JoinField中指定的父子名称,与原生语法是一致的
// case1: hasChild查询,返回的是相关的父文档 所以查询用父文档实体及其mapper
LambdaEsQueryWrapper<Document> documentWrapper = new LambdaEsQueryWrapper<>();
documentWrapper.hasChild("comment", w -> w.eq(FieldUtils.val(Comment::getCommentContent), "test1"));
List<Document> documents = documentMapper.selectList(documentWrapper);
System.out.println(documents);
LambdaEsQueryWrapper<Author> authorWrapper = new LambdaEsQueryWrapper<>();
authorWrapper.hasChild("contact", w -> w.match(FieldUtils.val(Contact::getAddress), "city"));
List<Author> authors = authorMapper.selectList(authorWrapper);
System.out.println(authors);
// case2: hasParent查询,返回的是相关的子文档 所以查询用子文档实体及其mapper
LambdaEsQueryWrapper<Comment> commentWrapper = new LambdaEsQueryWrapper<>();
commentWrapper.like(Comment::getCommentContent, "test");
// 字段名称你也可以不用FieldUtils.val,直接传入字符串也行
commentWrapper.hasParent("document", w -> w.match("content", "father").or().match("content", "content"));
commentWrapper.hasParent("document", w -> w.match("content", "father"));
List<Comment> comments = commentMapper.selectList(commentWrapper);
System.out.println(comments);
// case2.1: 孙子查爹的情况
LambdaEsQueryWrapper<Contact> contactWrapper = new LambdaEsQueryWrapper<>();
contactWrapper.hasParent("author", w -> w.eq(FieldUtils.val(Author::getAuthorName), "cat"));
List<Contact> contacts = contactMapper.selectList(contactWrapper);
System.out.println(contacts);
// case2.2: 2.1的简写
LambdaEsQueryWrapper<Contact> contactWrapper1 = new LambdaEsQueryWrapper<>();
// hasParent之所以可以不指定parentType简写是因为框架可以通过@Join注解中指定的父子关系自动推断出其父type,因此用户可以不指定父type直接查询,但hasChild不能简写,因为一个父亲可能有多个孩子,但一个孩子只能有一个亲爹
contactWrapper1.hasParent(w -> w.eq(FieldUtils.val(Author::getAuthorName), "cat"));
List<Contact> contacts1 = contactMapper.selectList(contactWrapper1);
System.out.println(contacts1);
// case3: parentId查询,返回的是相关的子文档,与case2类似,所以查询用子文档实体及其mapper
commentWrapper = new LambdaEsQueryWrapper<>();
commentWrapper.parentId("doc-1", "comment");
List<Comment> commentList = commentMapper.selectList(commentWrapper);
System.out.println(commentList);
contactWrapper = new LambdaEsQueryWrapper<>();
contactWrapper.parentId("author-2", "contact");
List<Contact> contactList = contactMapper.selectList(contactWrapper);
System.out.println(contactList);
}
@Test
@ -100,13 +177,19 @@ public class JoinTest {
Document document = new Document();
document.setEsId("doc-1");
document.setTitle("我是隔壁老王标题");
documentMapper.updateById(document);
documentMapper.updateById(FIXED_ROUTING, document);
Contact contact = new Contact();
contact.setContactId("contact-2");
contact.setAddress("update address");
contactMapper.updateById(FIXED_ROUTING, contact);
// case2: 父文档/子文档 根据各自条件更新
Comment comment = new Comment();
comment.setCommentContent("我是隔壁老王的评论");
comment.setCommentContent("update comment content");
LambdaEsUpdateWrapper<Comment> wrapper = new LambdaEsUpdateWrapper<>();
wrapper.match(Comment::getCommentContent, "comment");
wrapper.eq(Comment::getCommentContent, "test1");
wrapper.routing(FIXED_ROUTING);
commentMapper.update(comment, wrapper);
}
@ -114,11 +197,12 @@ public class JoinTest {
@Order(4)
public void testDelete() {
// case1: 父文档/子文档 根据各自的id删除
documentMapper.deleteById("doc-1");
documentMapper.deleteById(FIXED_ROUTING, "doc-1");
//case2: 父文档/子文档 根据各自条件删除
LambdaEsQueryWrapper<Comment> wrapper = new LambdaEsQueryWrapper<>();
wrapper.match(Comment::getCommentContent, "comment");
wrapper.like(Comment::getCommentContent, "test")
.routing(FIXED_ROUTING);
commentMapper.delete(wrapper);
}

View File

@ -16,7 +16,7 @@ easy-es:
field-strategy: not_empty
refresh-policy: immediate
enable-track-total-hits: true
# index-prefix: dev_
# index-prefix: dev_
dynamic:
datasource:
ds1: