Elastic Search(ES)Java 入门实操(2)搜索代码

上篇解释了 ES 的基本概念和分词器。Elastic Search (ES)Java 入门实操(1)下载安装、概念-CSDN博客

Elastic Search(ES)Java 入门实操(3)数据同步-CSDN博客

这篇主要演示 Java 整合 ES进行数据搜索。

ES 实现搜索接口

首先根据 MySQL 字段,在 ES 创建索引。

sql 复制代码
create table mydb
(
    id          int auto_increment comment '序号'
        primary key,
    title       varchar(20)                        not null comment '标题',
    content     text                               not null comment '内容',
    cretateTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
    updateTime  datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    isDelete    tinyint  default 0                 not null comment '是否删除'
)
    comment '文章' collate = utf8mb4_unicode_ci;
java 复制代码
PUT article_1
{
  "aliases": {  #别名
    "article": {}
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text", #字段类型
        "analyzer": "ik_max_word",#插入时分词方式
        "search_analyzer": "ik_smart", #查询时分词方式
        "fields": { #字段配置,子字段
          "keyword": { 
            "type": "keyword", #精确匹配
            "ignore_above": 256 #超过 256 字符就忽略查询
          }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "createTime": {
        "type": "date"
      },
      "updateTime": {
        "type": "date"
      },
      "isDelete": {
        "type": "keyword"
      }
    }
  }
}

引入 spring-data-elasticsearch 依赖

需要注意版本号一定要兼容

Spring Data Elasticsearch - Reference Documentation

这里使用的是 7.17版本,所以选择相近的 4.4.x版本的 依赖,同样的 springboot 的版本也得严格对应。Maven Repository: org.springframework.data >> spring-data-elasticsearch >> 4.4.7 (mvnrepository.com)

下面的报错就是版本不对,需要把springboot 改为 2.7+,

java 复制代码
java.lang.NoSuchFieldError: INDEX_CONTENT_TYPE
XML 复制代码
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-elasticsearch -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
    <version>4.4.7</version>
</dependency>

启动 springboot,有调用的日志

给 ES 索引创建实体类

java 复制代码
/**
 * ES 实体类
 * document 注解是将Java 对象映射到 Elasticsearch 索引和类型中
 */
@Document(indexName = "article")
@Data
public class ArticleEsDto implements Serializable {
    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

    /**
     * id
     * 需要打上 id 注解,指定 ES 中存储的 id 是唯一字段
     * 如果新增是不传入,则 ES 会自动生成
     */
    @Id
    private long id;
    /**
     * 标题
     */
    private String title;

    /**
     * 内容
     */
    private String content;

    /**
     * 创建时间
     */
    @Field(index = false, store = true,type = FieldType.Date,format = {},pattern = DATE_TIME_PATTERN)
    private Date createTime;

    /**
     * 更新时间
     * Field:这是一个MyBatis-Plus注解,用于标注该字段在数据库表中的对应关系。
     * 其中,index = false表示该字段不在数据库表的索引中,
     * store = true表示该字段在数据库表的存储中,
     * type = FieldType.Date表示该字段的类型为Date,
     * format = {}表示该字段的格式为空,
     * pattern = DATE_TIME_PATTERN表示该字段的日期时间格式为DATE_TIME_PATTERN。
     */
    @Field(index = false, store = true,type = FieldType.Date,format = {},pattern = DATE_TIME_PATTERN)
    private Date updateTime;

    /**
     * 是否删除
     */
    private Integer isDelete;

    private static final long serialVersionUID = 1L;
}

第一种方式

elasticsearch Respository,新建类继承该类,默认提供了简单的增删改查方法,多用于可以预期的,相对不复杂的查询

java 复制代码
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S entity);

    <S extends T> Iterable<S> saveAll(Iterable<S> entities);

    Optional<T> findById(ID id);

    boolean existsById(ID id);

    Iterable<T> findAll();

    Iterable<T> findAllById(Iterable<ID> ids);

    long count();

    void deleteById(ID id);

    void delete(T entity);

    void deleteAllById(Iterable<? extends ID> ids);

    void deleteAll(Iterable<? extends T> entities);

    void deleteAll();
}
java 复制代码
/**
 * ES 的控制层
 * 继承 ElasticsearchRepository 即可
 */
public interface ArticleEsDao extends ElasticsearchRepository<ArticleEsDto, Long> {

    // 这里可以扩展一些自定义方法
    // 例如:根据标题模糊查询
    List<ArticleEsDto> findByTitle(String title);

}

新增测试

java 复制代码
//注入接口
    @Resource
    private ArticleEsDao articleEsDao;
    
    //测试新增
    @Test
    void EsTest1(){
        //创建实体对象并添加属性
        ArticleEsDto articleEsDto = new ArticleEsDto();
        articleEsDto.setId(1L);
        articleEsDto.setTitle("青花瓷");
        articleEsDto.setContent("天青色等烟雨而我在等你");
        articleEsDto.setCreateTime(new Date());
        articleEsDto.setUpdateTime(new Date());
        articleEsDto.setIsDelete(0);
        //调用方法保存
        articleEsDao.save(articleEsDto);
        System.out.println(articleEsDto.getId());
    }

dev tools 查看

java 复制代码
GET article/_search/

自定义方法测试

我们在上面创建接口的时候创建了一个根据标题查询的方法

java 复制代码
    @Test
    void EsTest2(){
        List<ArticleEsDto> articleEsDtos = articleEsDao.findByTitle("青花瓷");
        System.out.println(articleEsDtos);
    }

第二种方式

spring 默认提供了操作 ES 的客户端对象 ElasticSearchRestTemplate,同样提供了增删改查,更加灵活,适用于更加复杂的操作,返回结果更加完整,但是需要自己解析。

java 复制代码
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

提示,在编写查询条件以及处理数据时,可以先在 Dev Tools 中执行一下查询,没问题之后再进行代码层面的条件编写。

查询 DSL

官方文档:Query and filter context | Elasticsearch Guide [8.14] | Elastic

查询模式:Boolean query | Elasticsearch Guide [8.14] | Elastic

java 复制代码
GET /_search
{
  "query": {  
    "bool": {  //组合条件
      "must": [ //必须匹配
        { "match":  // 模糊匹配{ "title":   "Search"        }},
        { "match": { "content": "Elasticsearch" }}
      ],
      "filter": [ //
        { "term": //精确匹配 { "status": "published" }},
        { "range": //范围匹配 { "publish_date": { "gte": "2015-01-01" }}}
      ]
    }
  }
}

除了 must ,filter,还有 must_not,必须不存在才能匹配,should,至少有多少个条件相符才匹配,同时还有一个参数 minimum_should_match 满足最小的条件数,比如 1,至少满足一个条件才能查询到,比如标题和描述存在一个就可以返回结果。

java 复制代码
POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user.id" : "kimchy" }
      },
      "filter": {
        "term" : { "tags" : "production" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 20 }
        }
      },
      "should" : [
        { "term" : { "tags" : "env1" } },
        { "term" : { "tags" : "deployed" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

需要注意的是,通过模糊查询之后,查询结果中有一个参数是 max_score,表示这条数据和搜索条件的最高匹配度。

在 Java 中编写查询条件以及处理查询的数据。

主要使用到的 API

java 复制代码
//查询条件构造器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

//排序条件构造器
SortBuilder<?> sortBuilder = SortBuilders.scoreSort();

//分页
PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize);

//组合查询条件
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
                .withPageable(pageRequest).withSorts(sortBuilder).build();
//调用 elasticsearchRestTemplate 查询
SearchHits<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);

完整代码

java 复制代码
/**
 * 从 es 查询数据
 */
@Service
public class ArticleEsManager {

    @Resource
    private ArticleMapper articleMapper;


    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    public Page<Article> searchByEs(ArticleQueryRequest articleQueryRequest) {
        //提取查询参数
        Long id = articleQueryRequest.getId();
        String searchText = articleQueryRequest.getSearchText();
        String content = articleQueryRequest.getContent();
        String title = articleQueryRequest.getTitle();
        //设置分页参数,起始页为 0
        int current = articleQueryRequest.getCurrent() -1 ;
        int pageSize = articleQueryRequest.getPageSize();
        String sortField = articleQueryRequest.getSortField();
        String sortOrder = articleQueryRequest.getSortOrder();
        //构建布尔查询,创建 boolqueryBuilder,用于后续查询条件构建
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //过滤查询条件
        //过滤掉被逻辑删除的 term 是精确匹配
        boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete",0));
        //id必须精确匹配
        if(id!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("id",id));
        }
        //模糊查询,按照查询词(关键词)检索
        if(StringUtils.isNotEmpty(searchText)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("title",searchText));
            boolQueryBuilder.should(QueryBuilders.matchQuery("content",searchText));
            //设置至少多少匹配才进行查询
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //根据标题检索
        if(StringUtils.isNotEmpty(title)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("title",title));
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //根据内容检索
        if(StringUtils.isNotEmpty(content)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("content",searchText));
            //设置至少多少匹配才进行查询
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //进行排序
        //对查询结果的分数进行排序
        SortBuilder<?> sortBuilder = SortBuilders.scoreSort();
        if (StringUtils.isNotEmpty(sortField) && StringUtils.isNotEmpty(sortOrder)){
            sortBuilder = SortBuilders.fieldSort(sortField);
            sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC:SortOrder.DESC);
        }
        //分页
        PageRequest pageRequest = PageRequest.of(current, pageSize);
        //构造查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
                .withPageable(pageRequest).withSorts(sortBuilder).build();
        //调用 elasticsearchRestTemplate 执行查询
        SearchHits<ArticleEsDto> searchHits = elasticsearchRestTemplate.search(searchQuery, ArticleEsDto.class);
        System.out.println(searchHits);
        Page<Article> page = new Page<>();
        page.setTotal(searchHits.getTotalHits());
        //新建集合存储文章
        List<Article> resourceList = new ArrayList<>();
        //处理结果,判断是否有搜索结果
        if(searchHits.hasSearchHits()){
            //获取结果列表
            List<SearchHit<ArticleEsDto>> searchHits1 = searchHits.getSearchHits();
            System.out.println(searchHits1);
            //获取结果的id,使用id在数据库中查询
            List<Long> ids = searchHits1.stream().map(searchHit -> searchHit.getContent().getId()).collect(Collectors.toList());
            List<Article> articles = articleMapper.selectBatchIds(ids);
            //将查询结果与数据库查询结果进行匹配
            if (CollectionUtils.isNotEmpty(articles)) {
                //将查询结果与数据库查询结果进行匹配
                Map<Long, List<Article>> collect = articles.stream().collect(Collectors.groupingBy(Article::getId));
                //遍历文章id
                articles.forEach(articleId -> {
                    if(collect.containsKey(articleId)){
                        resourceList.add(collect.get(articleId).get(0));
                    }else{
                        // 从 es 清空 db 已物理删除的数据
                        String delete = elasticsearchRestTemplate.delete(String.valueOf(articleId), ArticleEsDto.class);
                    }
                });
            }
        }
        page.setRecords(resourceList);
        return page;
    }
}

成功查询到数据

完整代码

java 复制代码
/**
 * 从 es 查询数据
 */
@Service
public class ArticleEsManager {

    @Resource
    private ArticleMapper articleMapper;


    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;


    public Page<Article> searchByEs(ArticleQueryRequest articleQueryRequest) {
        //提取查询参数
        Long id = articleQueryRequest.getId();
        String searchText = articleQueryRequest.getSearchText();
        String content = articleQueryRequest.getContent();
        String title = articleQueryRequest.getTitle();
        //设置分页参数,起始页为 0
        int current = articleQueryRequest.getCurrent() -1 ;
        int pageSize = articleQueryRequest.getPageSize();
        String sortField = articleQueryRequest.getSortField();
        String sortOrder = articleQueryRequest.getSortOrder();
        //构建布尔查询,创建 boolqueryBuilder,用于后续查询条件构建
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //过滤查询条件
        //过滤掉被逻辑删除的 term 是精确匹配
        boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete",0));
        //id必须精确匹配
        if(id!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("id",id));
        }
        //模糊查询,按照查询词(关键词)检索
        if(StringUtils.isNotEmpty(searchText)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("title",searchText));
            boolQueryBuilder.should(QueryBuilders.matchQuery("content",searchText));
            //设置至少多少匹配才进行查询
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //根据标题检索
        if(StringUtils.isNotEmpty(title)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("title",title));
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //根据内容检索
        if(StringUtils.isNotEmpty(content)){
            boolQueryBuilder.should(QueryBuilders.matchQuery("content",searchText));
            //设置至少多少匹配才进行查询
            boolQueryBuilder.minimumShouldMatch(1);
        }
        //进行排序
        //对查询结果的分数进行排序
        SortBuilder<?> sortBuilder = SortBuilders.scoreSort();
        if (StringUtils.isNotEmpty(sortField) && StringUtils.isNotEmpty(sortOrder)){
            sortBuilder = SortBuilders.fieldSort(sortField);
            sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC:SortOrder.DESC);
        }
        //分页
        PageRequest pageRequest = PageRequest.of(current, pageSize);
        //构造查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
                .withPageable(pageRequest).withSorts(sortBuilder).build();
        //调用 elasticsearchRestTemplate 执行查询
        SearchHits<ArticleEsDto> searchHits = elasticsearchRestTemplate.search(searchQuery, ArticleEsDto.class);
        System.out.println(searchHits);
        Page<Article> page = new Page<>();
        page.setTotal(searchHits.getTotalHits());
        //新建集合存储文章
        List<Article> resourceList = new ArrayList<>();
        //处理结果,判断是否有搜索结果
        if(searchHits.hasSearchHits()){
            //获取结果列表
            List<SearchHit<ArticleEsDto>> searchHits1 = searchHits.getSearchHits();
            System.out.println(searchHits1);
            //获取结果的id,使用id在数据库中查询
            List<Long> ids = searchHits1.stream().map(searchHit -> searchHit.getContent().getId()).collect(Collectors.toList());
            List<Article> articleList = articleMapper.selectBatchIds(ids);
            //将查询结果与数据库查询结果进行匹配
            if (CollectionUtils.isNotEmpty(articleList)) {
                //将查询结果与数据库查询结果进行匹配
                Map<Long, List<Article>> collect = articleList.stream().collect(Collectors.groupingBy(Article::getId));
                //遍历文章id
                ids.forEach(articleId -> {
                    if(collect.containsKey(articleId)){
                        resourceList.add(collect.get(articleId).get(0));
                    }else{
                        // 从 es 清空 db 已物理删除的数据
                        String delete = elasticsearchRestTemplate.delete(String.valueOf(articleId), ArticleEsDto.class);
                    }
                });
            }
        }
        //设置到分页里
        page.setRecords(resourceList);
        return page;
    }
相关推荐
WaaTong4 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048444 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries6 分钟前
Java字节码增强库ByteBuddy
java·后端
小灰灰__26 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭29 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
Elastic 中国社区官方博客1 小时前
如何将数据从 AWS S3 导入到 Elastic Cloud - 第 3 部分:Elastic S3 连接器
大数据·elasticsearch·搜索引擎·云计算·全文检索·可用性测试·aws
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
掘金-我是哪吒1 小时前
微服务mysql,redis,elasticsearch, kibana,cassandra,mongodb, kafka
redis·mysql·mongodb·elasticsearch·微服务
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el