自研ES-ORM

409 阅读10分钟

开发背景

为什么要自研 ES-ORM?

在字节实习的时候,负责的一个项目叫做数据中心。

数据中心是呼叫中心部门呼叫服务的数据中枢,其核心分为两大模块,一为向业务方推送各种类型的消息体, 一为对外提供 统一的话单信息RPC查询服务

这里 对外提供统一的话单信息RPC查询服务就需要去es里面根据各种条件做查询(话单类里面可能上百个字段,如果使用es原生api,那么将十分痛苦。。。)

所以实习期间搞了一套 ES-ORM 来简化代码开发。

同时 ES-ORM 可以屏蔽 ES-JAVA复杂的SDK。帮助大家更好的使用 ES 进行查询。只要你会使用 Mybatis-Plus 就能轻松使用 ES-ORM 对 ES进行查询

快速入门

条件构造器

概述

kdk-es-orm 提供了一套强大的条件构造器(EsWrapper),用于构建复杂的数据库查询条件。EsWrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 DSL 语句。

在 kdk-es-orm 中,EsWrapper 类是构建查询和更新条件的核心工具。

功能详解

三种基础构造

EsWrapper 被称之为最小的逻辑单元,他们之间的逻辑要么全是 && 要么全是 ||。无论是 of, andOf, orOf,你都需要传递你要查询的类对象。

of构造

of构造一般用于单条件查询,如下

of(Student.class).eq("name", "qyh");
of(Student.class).eq(Student::getName, "qyh");

等价SQL:

select * from student where name = 'qyh'

andOf构造

andOf 后面链式调用的条件 他们之间的逻辑关系是 &&

andOf(Student.class).eq("name", "qyh").eq("sex", 1);

等价SQL:

select * from student where name = 'qyh' and sex = 1

orOf构造

andOf 后面链式调用的条件 他们之间的逻辑关系是 ||

orOf(Student.class).eq("name", "qyh").eq("sex", 1);

等价SQL:

select * from student where name = 'qyh' or sex = 1

实体类

实体类就是你要从es里面查询出来的类,在字节实习的时候 一条话单记录就是实体类。

这里使用 Student类 举例子。

@Data
@AllArgsConstructor
@NoArgsConstructor
@Index(indexName = "student")
public class Student {
    
    @DocumentId(documentId = "studentId")
    private Long studentId;

    private String name;

    private String school;

    private Integer age;

    private Integer classNo;

    private Integer sex;

    // 逻辑删除字段 isDelete 0 代表逻辑删除
    @LogicDelete(filedName = "isDelete", deleteValue = 0)
    private Integer isDelete;
}

ps:

  1. @Index(indexName = "student") 注解指明了该实体类所在索引名字。如果不标注 @Index 注解,那么索引默认取类名小写,即 student
  2. @DocumentId 指定的是索引主键,如果不指定,默认取 "id"
  3. @LogicDelete(filedName = "isDelete", deleteValue = 0) 指定 isDelete 是逻辑删除字段,并且 delelteValue = 0 代表逻辑删除的值

StudentService

public class StudentService extends BaseService<Student> {

}

你需要继承 BaseService,并且指定你要crud的类 Student。其中BaseService提供一些基础的 查询。 例如根据id查询,根据ids查询,根据条件查询一条数据or多条数据

图片.png

使用示例

准备一个类 StudentQueryDTO ,用来接收查询Student的查询条件

@Data
@AllArgsConstructor
@Builder
public class StudentQueryDTO {
    private Long id;
    
    private String name;
    private List<Object> names;

    private Integer age;
    private Integer fromAge;
    private Integer toAge;
    private List<Object> ages;

    private String school;
    private List<Integer> schools;

    private Integer sex;

    private Integer classNo;
    private Integer fromClassNo;
    private Integer toClassNo;
    private List<Integer> classNos;

}

根据ID查询单个Student

public static void testQueryById() {

    /**
     * equal sql :
     * select * from student where studentId = 111
     */

    Student student = studentService.getById(111);
}

根据id查询列表 student


public static void testListByIds() {

    /**
     * equal sql :
     * select * from student where studentId in (1, 2, 3)
     */

    List<Student> students = studentService.listByIds(asList(1, 2, 3));
}

普通查询


public static void testGetOneNormal() {

    StudentQueryDTO queryDTO = StudentQueryDTO
            .builder()
            .age(15)
            .build();

    /**
     * equal sql :
     * select * from student where age = 15
     */

    EsWrapper<Student> ageWrapper = of(Student.class).eq("age", 15);
    Student student = studentService.getOne(ageWrapper);
}

andOf 之间是 && 的逻辑

public static void queryByAndCondition()
{
    EsWrapper<Student> esWrapper = andOf(Student.class)
        .eq("age", 15)
        .eq("name", "qyh");

    /**
      * equal sql :
      * SELECT * FROM student WHERE age = 15 AND name = 'qyh'
      */

    Student student = studentService.getOne(esWrapper);
}

orOf 之间是 || 的逻辑

public static void queryByOrCondition()
{
    EsWrapper<Student> esWrapper = orOf(Student.class)
        .eq("age", 15)
        .eq("name", "qyh");

    /**
      * equal sql :
      * SELECT * FROM student WHERE age = 15 OR name = 'qyh'
      */

    Student student = studentService.getOne(esWrapper);
}

指定需要查询的字段

public static void testGetOneNormal3() {

    StudentQueryDTO queryDTO = StudentQueryDTO
            .builder()
            .age(15)
            .name("qyh")
            .build();

    /**
     * equal sql :
     * select age, name  from student where age = 15 and name = 'qyh'
     */

    EsWrapper<Student> ageNameAndWrapper = of(Student.class)
            .eq("age", queryDTO.getAge())
            .eq("name", queryDTO.getName())
            .includeFields(Student::getName, Student::getAge);

    Student student = studentService.getOne(ageNameAndWrapper);
}

conditionApi简化开发

public static void testGetOneConditionApi() {

    StudentQueryDTO queryDTO = StudentQueryDTO
            .builder()
            .age(15)
            .name("qyh")
            .build();

    /**
     * equal sql :
     * 1. queryDTO.age == null && queryDTO.name == null
     *    select * from student;
     * 2. queryDTO.age == null && queryDTO.name != null
     *    select * from student where name = 'qyh'
     * 3. queryDTO.age != null && queryDTO.name == null
     *    select * from student where age = 15
     * 4. queryDTO.age != null && queryDTO.name != null
     *    select * from student where age = 15 and name = 'qyh'
     */

    EsWrapper<Student> ageNameAndWrapper = of(Student.class)
            .eq(nonNull(queryDTO.getAge()), "age", queryDTO.getAge())
            .eq(nonNull(queryDTO.getName()), "name", queryDTO.getName());

    Student student = studentService.getOne(ageNameAndWrapper);
}

使用Lambda表达式屏蔽列名概念。

public static void testGetOneByLambdaApi() {

    StudentQueryDTO queryDTO = StudentQueryDTO
            .builder()
            .age(15)
            .name("qyh")
            .build();

    /**
     * equal sql :
     * 1. queryDTO.age == null && queryDTO.name == null
     *    select * from student;
     * 2. queryDTO.age == null && queryDTO.name != null
     *    select * from student where name = 'qyh'
     * 3. queryDTO.age != null && queryDTO.name == null
     *    select * from student where age = 15
     * 4. queryDTO.age != null && queryDTO.name != null
     *    select * from student where age = 15 and name = 'qyh'
     */

    EsWrapper<Student> ageNameAndWrapper = of(Student.class)
            .eq(nonNull(queryDTO.getAge()), Student::getAge, queryDTO.getAge())
            .eq(nonNull(queryDTO.getName()), Student::getName, queryDTO.getName());

    Student student = studentService.getOne(ageNameAndWrapper);
}

根据条件查询列表

public static void testListByCondition() {

    /**
     * equal sql :
     * select * from student where studentId in (1, 2, 3)
     */

    EsWrapper<Student> esWrapper = of(Student.class)
            .in(Student::getStudentId, asList(1, 2, 3));

    List<Student> res = studentService.list(esWrapper);
}

指定 from 和 limit

public static void testPageListByCondition() {

    /**
     * equal sql :
     * select * from student where studentId in (1, 2, 3) from 0 limit 3
     */

    EsWrapper<Student> esWrapper = of(Student.class)
            .in(Student::getStudentId, asList(1, 2, 3))
            .from(0)
            .size(3);

    List<Student> res = studentService.list(esWrapper);
}

自动过滤掉逻辑删除的数据

public static void testFilterLogicDelete() {

    /**
     * equal sql :
     * select * from student where age in (1, 2, 3) and is_delete != 0 from 10 limit 20
     */

    EsWrapper<Student> esWrapper = of(Student.class, FILTER_LOGIC_DELETE)
            .in(Student::getAge, Lists.newArrayList(1, 2, 3))
            .from(10)
            .size(20);

    List<Student> list = studentService.list(esWrapper);
}

ps:这种方式需要在 Student 类的字段上添加注解 @LogicDelete 标明哪个是逻辑删除字段,并且指定逻辑删除的值

指定结果排序方式

public static void testOrderBy() {

    /**
     * equal sql :
     * select * from student
     * where age in (1, 2, 3) and is_delete != 0
     * from 10 limit 20
     * order by studentId desc
     */

    EsWrapper<Student> esWrapper = of(Student.class, FILTER_LOGIC_DELETE)
            .in(Student::getAge, Lists.newArrayList(1, 2, 3))
            .from(10)
            .size(20)
            .orderBy(Student::getStudentId, DESC);

    List<Student> list = studentService.list(esWrapper);
}

复杂逻辑

这里通过 WrapperLogicOpHelper 可以构建 wrapper之间的任意亦或逻辑

public static void testLogicApi() {

    StudentQueryDTO queryDTO = StudentQueryDTO
        .builder()
        .age(15)
        .name("qyh")
        .sex(1)
        .build();

    /*
          equal sql :
          select * from student where (age = 15  and name = 'qyh') or sex = 1
         */

    EsWrapper<Student> finalEsWrapper = EsWrapperLogicHelper.or
        (
        andOf(Student.class)
            .eq(Student::getAge, queryDTO.getAge())
            .eq(Student::getName, queryDTO.getName()),
        of(Student.class).eq(Student::getSex, queryDTO.getSex())
        );

    List<Student> list = studentService.list(finalEsWrapper);
}


public static void testLogicApi2() {

    StudentQueryDTO queryDTO = StudentQueryDTO
        .builder()
        .age(15)
        .name("qyh")
        .sex(1)
        .build();

    /**
      * equal sql :
      * select * from student where (age = 15  or name = 'qyh') and sex = 1
      */

    EsWrapper<Student> finalWrapper = and
        (
        orOf(Student.class)
           .eq(Student::getName, queryDTO.getName())
           .eq(Student::getAge, queryDTO.getAge()),
        of(Student.class).eq(Student::getSex, queryDTO.getSex())
        );

    List<Student> list = studentService.list(finalWrapper);
}


public static void testLogicApi3() {

    StudentQueryDTO queryDTO = StudentQueryDTO
        .builder()
        .age(15)
        .name("qyh")
        .sex(1)
        .classNo(14)
        .build();

   /**
     * equal sql :
     * select * from student where (age = 15  or name = 'qyh' or classNo = 14) and sex = 1
    */

    EsWrapper<Student> finalEsWrapper = and
        (
         orOf(Student.class)
           .eq(Student::getName, queryDTO.getName())
           .eq(Student::getAge, queryDTO.getAge())
           .eq(Student::getClassNo, queryDTO.getClassNo()),
         of(Student.class).eq(Student::getSex, queryDTO.getSex())
        );

    List<Student> list = studentService.list(finalEsWrapper);
}

设计原理

泛型实例化

简而言之就是继承泛型类,然后指定泛型类型。然后就能拿到实例化的泛型,通过反射分析上面的注解,获得索引名称,主键字段。

下面是一个使用的例子。

public static void main(String[] args) {
        
        StudentService studentService = new StudentService();
        Student student = studentService.findById(1L);

        TeacherService teacherService = new TeacherService();
        Teacher teacher = teacherService.findById(2L);

        //最后 生成的sql是 : select * from hdu_student where id = 1
        //最后 生成的sql是 : select * from teacher where id = 2
    }


    static class IService<T> {

        //获得实体类型
        Class<T> getEntityClass() {
            //获得子类 并获得子类所继承父类的泛型信息
            Type genericSuperclass = this.getClass().getGenericSuperclass();
            if (genericSuperclass instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) genericSuperclass;
                Type actualTypeArgument = pt.getActualTypeArguments()[0];
                return (Class<T>) actualTypeArgument;
            } else {
                throw new RuntimeException("没有获取到合适的泛型信息");
            }
        }

        //根据id查询一条数据
        T findById(Long id) {

            String tableName = getTableName();

            //最后查询的sql是
            System.out.println("最后 生成的sql是 : select * from " + tableName + " where id = " + id);


            //该方法还可以扩展 因为我们已经有 class信息了
            //那么我们可以去获得各种各样的字段 , 去解析字段上的注解......
            return null;
        }

        //获得该实体类的表名
        String getTableName() {
            String tableName = null;
            Class<?> entityClass = getEntityClass();
            TableName tableNameAnnotation = entityClass.getAnnotation(TableName.class);
            if (tableNameAnnotation != null) {
                tableName = tableNameAnnotation.value();
            } else {
                //如果没有添加注解 那么就获得它的简单类名
                //然后把首字母转化为小写 比如 Teacher -> teacher
                tableName = entityClass.getSimpleName();
                tableName = Character.toLowerCase(tableName.charAt(0)) + tableName.substring(1);
            }
            return tableName;
        }

       
        void save(T t) {
            System.out.println("调用 ? 类型的 save");
        }
    }


    //已经确定的泛型
    static class StudentService extends IService<Student> {

    }

    //已经确定的泛型
    static class TeacherService extends IService<Teacher> {

    }
}


//指定entity对应的表名是 hdu_student
@TableName("hdu_student")
class Student {

}


//@TableName("hdu_teacher")
class Teacher {

}

//可以放在实体类上面 指定他对应数据库的表名是什么
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface TableName {
    String value() default "";
}

通过方法引用来屏蔽列名概念

public static void testGetOneByLambdaApi() {

    StudentQueryDTO queryDTO = StudentQueryDTO
            .builder()
            .age(15)
            .name("qyh")
            .build();

    /**
     * equal sql :
     * 1. queryDTO.age == null && queryDTO.name == null
     *    select * from student;
     * 2. queryDTO.age == null && queryDTO.name != null
     *    select * from student where name = 'qyh'
     * 3. queryDTO.age != null && queryDTO.name == null
     *    select * from student where age = 15
     * 4. queryDTO.age != null && queryDTO.name != null
     *    select * from student where age = 15 and name = 'qyh'
     */

    Wrapper ageNameAndWrapper = new LambdaEsWrapper<Student>()
            .eq(nonNull(queryDTO.getAge()), Student::getAge, 15)
            .eq(nonNull(queryDTO.getName()), Student::getName, "qyh");

    Student student = studentService.getOne(ageNameAndWrapper);
}

上面传参 Student::getAge, Student::getName 。会被底层接收为 SFunction(一个可以序列化的Lambda表达式)

然后其中的 implMethodName 属性 就是调用的方法名,再取 getXxx 的 Xxx部分就是属性名,这样就完成了对列名概念的屏蔽。

output.png

底层核心模型 QueryFilter

在字节实习的时候,搞了一套 JavaBean -> QueryDSL 的转化模型。下面举个例子来说明一下该模型。

public class QueryFilter {

    String field;
    String exp;

    FilterType type;

    List<QueryFilter> must;
    List<QueryFilter> mustNot;
    List<QueryFilter> should;

    List<Object> in;
    List<Object> notIn;
    Object eq;
    Object notEq;
    Object gt;
    Object gte;
    Object lt;
    Object lte;
}


public enum FilterType {
    
    // 嵌套
    NESTED,
    // 等于
    EQ,
    // 不等于
    NOT_EQ,
    // 属于
    IN,
    // 不属于
    NOT_IN,
    // 范围
    RANGE,
    // 存在
    EXISTS,
    // 不存在
    NOT_EXISTS,
    // 脚本表达式
    EXP
}

例子

图片.png

图片.png

QueryFilter -> BoolQueryBuilder 的转换

package com.hdu.kdk_es_orm.core;

import lombok.val;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.elasticsearch.script.Script;

import java.util.Objects;

import static com.hdu.kdk_es_orm.core.FilterType.LIKE;
import static com.hdu.kdk_es_orm.core.FilterType.LIKE_LEFT;
import static com.hdu.kdk_es_orm.core.FilterType.LIKE_RIGHT;
import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.scriptQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;


/**
 * 提供 boolQueryFilter -> QueryBuilder 功能
 */

public class EsQueryBuilderBuildHelper {

    private static final String LIKE_QUERY_TEMPLATE = "*%s*";
    private static final String LIKE_LEFT_QUERY_TEMPLATE = "*%s";
    private static final String LIKE_RIGHT_QUERY_TEMPLATE = "%s*";

    public static QueryBuilder convertToQueryBuilder(QueryFilter queryFilter) {

        if (queryFilter == null) {
            return null;
        }

        val boolQuery = QueryBuilders.boolQuery();
        val type = queryFilter.getType();
        switch (type) {
            case NESTED:
                buildNestedQuery(queryFilter, boolQuery);
                return boolQuery;
            case EQ:
                return termQuery(queryFilter.getField(), queryFilter.getEq());
            case NOT_EQ:
                return boolQuery.mustNot(termQuery(queryFilter.getField(), queryFilter.getNotEq()));
            case RANGE:
                return buildRangeQuery(queryFilter);
            case NOT_RANGE:
                return boolQuery.mustNot(buildRangeQuery(queryFilter));
            case IN:
                return termsQuery(queryFilter.getField(), queryFilter.getIn());
            case NOT_IN:
                return boolQuery.mustNot(termsQuery(queryFilter.getField(), queryFilter.getNotIn()));
            case EXISTS:
                return existsQuery(queryFilter.getField());
            case NOT_EXISTS:
                return boolQuery.mustNot(existsQuery(queryFilter.getField()));
            case EXP:
                return scriptQuery(new Script(queryFilter.getExp()));
            case LIKE:
                return buildWildcardQuery(
                    queryFilter.getField(),
                    format(LIKE_QUERY_TEMPLATE, queryFilter.getLike()),
                    LIKE
                );
            case LIKE_LEFT:
                return buildWildcardQuery(
                    queryFilter.getField(),
                    format(LIKE_LEFT_QUERY_TEMPLATE, queryFilter.getLikeLeft()),
                    LIKE_LEFT
                );
            case LIKE_RIGHT:
                return buildWildcardQuery(
                    queryFilter.getField(),
                    format(LIKE_RIGHT_QUERY_TEMPLATE, queryFilter.getLikeRight()),
                    LIKE_RIGHT
                );
            case NOT_LIKE:
                return buildNotWildcardQuery(
                    queryFilter.getField(),
                    format(LIKE_QUERY_TEMPLATE, queryFilter.getNotLike()),
                    LIKE
                );
            case NOT_LIKE_LEFT:
                return buildNotWildcardQuery(
                    queryFilter.getField(),
                    format(LIKE_LEFT_QUERY_TEMPLATE, queryFilter.getNotLikeLeft()),
                    LIKE_LEFT
                );
            case NOT_LIKE_RIGHT:
                return buildNotWildcardQuery(
                    queryFilter.getField(),
                    format(LIKE_RIGHT_QUERY_TEMPLATE, queryFilter.getNotLikeRight()),
                    LIKE_RIGHT
                );
            default:
                throw new UnsupportedOperationException("Unsupported FilterType: " + type);
        }
    }

    private static void buildNestedQuery(QueryFilter queryFilter, BoolQueryBuilder boolQuery) {
        val mustList = queryFilter.getMust();
        if (mustList != null && !mustList.isEmpty()) {
            mustList.stream()
                .map(EsQueryBuilderBuildHelper::convertToQueryBuilder)
                .filter(Objects::nonNull)
                .forEach(boolQuery::must);
        }

        val mustNotList = queryFilter.getMustNot();
        if (mustNotList != null && !mustNotList.isEmpty()) {
            mustNotList.stream()
                .map(EsQueryBuilderBuildHelper::convertToQueryBuilder)
                .filter(Objects::nonNull)
                .forEach(boolQuery::mustNot);
        }

        val shouldList = queryFilter.getShould();
        if (shouldList != null && !shouldList.isEmpty()) {
            shouldList.stream()
                .map(EsQueryBuilderBuildHelper::convertToQueryBuilder)
                .filter(Objects::nonNull)
                .forEach(boolQuery::should);
            boolQuery.minimumShouldMatch(1);
        }
    }

    private static QueryBuilder buildRangeQuery(QueryFilter queryFilter) {
        val rangeQueryBuilder = rangeQuery(queryFilter.getField());
        ofNullable(queryFilter.getGt()).ifPresent(rangeQueryBuilder::gt);
        ofNullable(queryFilter.getGte()).ifPresent(rangeQueryBuilder::gte);
        ofNullable(queryFilter.getLt()).ifPresent(rangeQueryBuilder::lt);
        ofNullable(queryFilter.getLte()).ifPresent(rangeQueryBuilder::lte);
        return rangeQueryBuilder;
    }

    private static QueryBuilder buildWildcardQuery(String filed, Object v, FilterType type) {
        String template;
        switch (type) {
            case LIKE:
                template = LIKE_QUERY_TEMPLATE;
                break;
            case LIKE_LEFT:
                template = LIKE_LEFT_QUERY_TEMPLATE;
                break;
            case LIKE_RIGHT:
                template = LIKE_RIGHT_QUERY_TEMPLATE;
                break;
            default:
                throw new UnsupportedOperationException("Unsupported FilterType: " + type);
        }
        return new WildcardQueryBuilder(
            filed,
            format(template, v)
        );
    }

    private static BoolQueryBuilder buildNotWildcardQuery(String filed, Object v, FilterType type) {
        return boolQuery().mustNot(
            buildWildcardQuery(filed, v, type)
        );
    }
}

链式调用修改QueryFilter

举个例子

每个 Wrapper 底层都有一个基础 QueryFilter。基础 QueryFilter类型是 NESTED 类型,它有一个must数组。每次链式调用其实都是往 基础 QueryFilter里面的 must数组塞各种类型的QueryFilter,那么我们也可以得知:单个 Wrapper之间各种链式调用逻辑之间是且的关系。下面拿eq()方法举个例子。

// where age = 15
of(Student.class).eq(nonNull(queryDTO.getAge(), "age", 15);
public NormalEsWrapper<T> eq(boolean condition, String field, Object eq) {
    if (condition) {
        // 构建 age = 15 的 QueryFilter -> eqQueryFilter 
        QueryFilter eqQueryFilter = new QueryFilter();
        eqQueryFilter.setType(FilterType.EQ);
        eqQueryFilter.setField(field);
        eqQueryFilter.setEq(eq);

        // 获得最上层 QueryFilter
        List<QueryFilter> must = this.getQueryFilter().getMust();
        
        // 获得其must数组(对应的就是 && 逻辑)
        must = ofNullable(must).orElseGet(ArrayList::new);
        // 将 eqQueryFilter 添加到 最上层的 QueryFilter的 must数组
        
        // 从这里看出来  单个Wrapper链式调用之间的亦或逻辑是 &&
        must.add(eqQueryFilter);
         
        this.getQueryFilter().setMust(must);
    }
    // 链式调用返回
    return this;
}

真实例子

public static void testLogicApi3() {  
  
      StudentQueryDTO queryDTO = StudentQueryDTO  
                                 .builder()  
                                 .age(15)  
                                 .name("qyh")  
                                 .sex(1)  
                                 .classNo(14)  
                                 .build();  
  
          /**  
          * equal sql :  
          * select * from student where (age = 15 or name = 'qyh' or classNo = 14) and sex = 1 
          */  
  
          EsWrapper<Student> ageWrapper = of(Student.class)  
               .eq(nonNull(queryDTO.getAge()), Student::getAge, queryDTO.getAge());  
  
          EsWrapper<Student> nameWrapper = of(Student.class)  
               .eq(nonNull(queryDTO.getName()), Student::getName, queryDTO.getName());  
  
          EsWrapper<Student> classNoWrapper = of(Student.class)  
               .eq(nonNull(queryDTO.getClassNo()), Student::getClassNo, queryDTO.getClassNo());  
  
          EsWrapper<Student> ageNameClassNoOrWrapper = 
               EsWrapperLogicHelper.or(ageWrapper, nameWrapper, classNoWrapper);  
  
          EsWrapper<Student> sexWrapper = of(Student.class)  
               .eq(nonNull(queryDTO.getSex()), Student::getSex, queryDTO.getSex());  
  
          EsWrapper<Student> finalWrapper = 
               EsWrapperLogicHelper.and(ageNameClassNoOrWrapper, sexWrapper);  
               
          Student student = studentService.getOne(finalWrapper);  

}

最终 finalWrapper长这样

无标题.png

无标题.png

无标题.png

finalWrapper对应的QueryFilter

{
        "type": "NESTED",
        "must": [{
                "type": "NESTED",
                "should": [{
                        "type": "NESTED",
                        "must": [{
                                "eq": 15,
                                "type": "EQ",
                                "field": "age"
                        }]
                }, {
                        "type": "NESTED",
                        "must": [{
                                "eq": "qyh",
                                "type": "EQ",
                                "field": "name"
                        }]
                }, {
                        "type": "NESTED",
                        "must": [{
                                "eq": 14,
                                "type": "EQ",
                                "field": "classNo"
                        }]
                }]
        }, {
                "type": "NESTED",
                "must": [{
                        "eq": 1,
                        "type": "EQ",
                        "field": "sex"
                }]
        }]
}