SpringBoot+ElasticSearch 实现全文检索

ElasticSearch环境搭建:https://blog.csdn.net/u014553029/article/details/106009344

SpringBoot集成ElasticSearch实战

一、在pom.xml中引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-elasticsearch -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
    <version>3.2.6.RELEASE</version>
</dependency>

spring-boot-starter-data-elasticsearch包,引用的是spring-data-elasticsearch包,而spring-data-elasticsearch包的版本与elasticsearch服务版本是有兼容性问题的。具体可参考:https://github.com/spring-projects/spring-data-elasticsearch

二、在配置文件中添加相关配置

spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 127.0.0.1:9300
      repositories:
        enabled: true

三、添加实体ESBlog

使用@Document注解,参数indexName是索引名称,type是type名称。

注解说明:

  • @Document 作用在类,标记实体类为文档对象,一般有两个属性
    • indexName:对应索引库名称
    • type:对应在索引库中的类型
    • shards:分片数量,默认5
    • replicas:副本数量,默认1
  • @Id 作用在成员变量,标记一个字段作为id主键
  • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
    • type:字段类型,是是枚举:FieldType,可以是text、long、short、date、integer、object等
      • text:存储数据时候,会自动分词,并生成索引
      • keyword:存储数据时候,不会分词建立索引
      • Numerical:数值类型,分两类
        • 基本数据类型:long、interger、short、byte、double、float、half_float
        • 浮点数的高精度类型:scaled_float
          • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
      • Date:日期类型
        • elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
    • index:是否索引,布尔类型,默认是true
    • store:是否存储,布尔类型,默认是false
    • analyzer:分词器名称,这里的ik_max_word即使用ik分词器
    • searchAnalyzer :搜索使用分词器
package com.oycbest.springbootelasticsearch.document;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;

/**
 * @Author: oyc
 * @Date: 2020-04-30 9:37
 * @Description: Blog 文档实体类
 */
@Data
@Document(indexName = "blog_index", type = "blog")
public class EsBlog {

    @Id
    private int id;
    /**
     * 是否索引: 看该域是否能被搜索, index = true(默认为true)
     * 是否分词: 表示搜索的时候是整体匹配还是单词匹配
     * 是否存储: 是否在页面上显示
     */
    @Field(index = true, analyzer = "ik_smart", searchAnalyzer = "ik_smart")
    private String title;

    @Field(analyzer = "ik_smart", searchAnalyzer = "ik_smart")
    private String content;

    public EsBlog() {
    }

    public EsBlog(int id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }
}

五、添加ESBlog对应的ESBlogSearchRepository

package com.oycbest.springbootelasticsearch.repository;

import com.oycbest.springbootelasticsearch.document.EsBlog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

/**
 * @Author: oyc
 * @Date: 2020-04-30 9:40
 * @Description: ESBlog Repository
 */
@Repository
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog, Integer> {
    /**
     * 根据title或者内容分页查询
     *
     * @param title   标题
     * @param content 内容
     * @param page    分页
     * @return
     */
    Page<EsBlog> findByTitleOrContentLike(String title, String content, Pageable page);
}

六、在Service或者Controller中使用ESBlogSearchRepository或者ElasticsearchTemplate

我这里使用controller、service、serviceImpl获取信息

(1)EsBlogService

package com.oycbest.springbootelasticsearch.service;

import com.oycbest.springbootelasticsearch.document.EsBlog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

/**
 * @Author: oyc
 * @Date: 2020-04-30 9:38
 * @Description: Blog服务接口
 */
public interface EsBlogService {
    /**
     * 保存blog到ES
     * @param blog
     */
    void save(EsBlog blog);

    void save(List<EsBlog> blogs);

    void delete(int id);

    EsBlog getById(int id);

    Page<EsBlog> getByKey(String key, Pageable pageable);

    Page<EsBlog> getByKeyWord(String key, Pageable pageable);
}

(2)EsBlogServiceImpl

package com.oycbest.springbootelasticsearch.service.impl;

import com.oycbest.springbootelasticsearch.document.EsBlog;
import com.oycbest.springbootelasticsearch.repository.EsBlogRepository;
import com.oycbest.springbootelasticsearch.service.EsBlogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;

import static org.elasticsearch.index.query.QueryBuilders.matchQuery;

/**
 * @Author: oyc
 * @Date: 2020-04-30 9:39
 * @Description: Blog服务实现类
 */
@Service
public class EsBlogServiceImpl implements EsBlogService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private EsBlogRepository blogSearchRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Override
    public void save(EsBlog blog) {
        blogSearchRepository.save(blog);
        logger.debug("saved");
    }
    @Override
    public void save(List<EsBlog> blogs) {
        blogSearchRepository.saveAll(blogs);
        logger.debug("saved");
    }

    @Override
    public void delete(int id) {
        blogSearchRepository.deleteById(id);
    }

    @Override
    public EsBlog getById(int id) {
        EsBlog esBlog = blogSearchRepository.findById(id).orElse(new EsBlog());
        logger.debug(esBlog.toString());
        return esBlog;

    }

    @Override
    public Page<EsBlog> getByKey(String key, Pageable pageable) {
        if(StringUtils.isEmpty(key)){
            return blogSearchRepository.findAll(pageable);
        }
        return blogSearchRepository.findByTitleOrContentLike(key, key, pageable);
    }

    @Override
    public Page<EsBlog> getByKeyWord(String key, Pageable pageable) {
        if(StringUtils.isEmpty(key)){
            return blogSearchRepository.findAll(pageable);
        }
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(matchQuery("title", key))
                .withQuery(matchQuery("content", key))
                .withPageable(pageable)
                .build();
        Page<EsBlog> esBlogs = elasticsearchTemplate.queryForPage(searchQuery, EsBlog.class);
        esBlogs.forEach(e -> logger.debug(e.toString()));
        return esBlogs;
    }
}

(3)EsBlogController

package com.oycbest.springbootelasticsearch.controller;

import com.oycbest.springbootelasticsearch.document.Blog;
import com.oycbest.springbootelasticsearch.document.EsBlog;
import com.oycbest.springbootelasticsearch.repository.BlogRepository;
import com.oycbest.springbootelasticsearch.service.EsBlogService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: blog控制类
 * @Author oyc
 * @Date 2020/5/10 10:35 下午
 */
@RestController
@RequestMapping("blog")
public class EsBLogController {

    @Resource
    private BlogRepository blogRepository;

    @Resource
    private EsBlogService searchService;

    @GetMapping("init")
    private String initBlog() {
        List<Blog> blogs = blogRepository.findAll();
        List<EsBlog> esBlogs = new ArrayList<>();
        blogs.forEach(blog -> {
                    esBlogs.add(new EsBlog(blog.getId(), blog.getTitle(), blog.getContent()));
                }
        );
        searchService.save(esBlogs);
        return "init Success";
    }

    /**
     * @param blog 博客文档
     * @return
     */
    @PostMapping("save")
    public void save(EsBlog blog) {
        searchService.save(blog);
    }

    /**
     * @param id 文档id
     * @return
     */
    @GetMapping("getById")
    public Object getById(int id) {
        return searchService.getById(id);
    }

    /**
     * @param key 关键字
     * @return
     */
    @GetMapping("search")
    public Page<EsBlog> getByKey(HttpServletRequest request, String key) {
        Pageable pageable = getPageByRequest(request);
        return searchService.getByKey(key, pageable);
    }

    /**
     * @param key 关键字
     * @return
     */
    @GetMapping("keyWord")
    public Page<EsBlog> getByKeyWord(HttpServletRequest request, String key) {
        Pageable pageable = getPageByRequest(request);
        return searchService.getByKeyWord(key, pageable);
    }

    private Pageable getPageByRequest(HttpServletRequest request) {
        int page = StringUtils.isEmpty(request.getParameter("page")) ? 1 : Integer.parseInt(request.getParameter("page"));
        int size = StringUtils.isEmpty(request.getParameter("size")) ? 10 : Integer.parseInt(request.getParameter("size"));
        Pageable pageable = PageRequest.of(page - 1, size);
        return pageable;
    }
}

七、操作效果

(1) 查询标题或者内容包含“框架”的blog:http://127.0.0.1:8888/blog/search?key=框架

(2)查询标题或者内容包含“搭建”的blog:http://127.0.0.1:8888/blog/search?key=搭建

高亮效果:


    /**
     * 根据搜索结果创建esdoc对象
     *
     * @param smap
     * @param hmap
     * @return
     */
    private EsBlog createEsDoc(Map<String, Object> smap, Map<String, HighlightField> hmap) {
        EsBlog esBlog = new EsBlog();
        if (hmap.get("title") != null) {
            esBlog.setTitle(hmap.get("title").fragments()[0].toString());
        } else if (smap.get("title") != null) {
            esBlog.setTitle(smap.get("title").toString());
        }
        if (hmap.get("content") != null) {
            esBlog.setContent(hmap.get("content").fragments()[0].toString());
        } else if (smap.get("content") != null) {
            esBlog.setContent(smap.get("content").toString());
        }
        if (smap.get("id") != null) {
            esBlog.setId((Integer) smap.get("id"));
        }
        return esBlog;
    }


    @Override
    public Page<EsBlog> queryForPage(String key, Pageable pageable) {
        // 如果输入的查询条件为空,则查询所有数据
        if (StringUtils.isEmpty(key)) {
            return blogSearchRepository.findAll(pageable);
        }
        String preTag = "<font color='#dd4b39'>";
        String postTag = "</font>";
        SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(
                    QueryBuilders.boolQuery()
                        .should(QueryBuilders.matchQuery("title", key))
                        .should(QueryBuilders.matchQuery("content", key))
                )
                .withPageable(pageable)
                .withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC))
                //多字段高亮
                .withHighlightFields(
                        new HighlightBuilder.Field("title").preTags(preTag).postTags(postTag), 
                        new HighlightBuilder.Field("content").preTags(preTag).postTags(postTag)
                )
                .build();


        Page<EsBlog> esBlogs = elasticsearchTemplate.queryForPage(searchQuery, EsBlog.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<EsBlog> chunk = new ArrayList<>();
                for (SearchHit searchHit : response.getHits().getHits()) {
                    // 普通查询结果
                    Map<String, Object> smap = searchHit.getSourceAsMap();
                    // 高亮查询结果
                    Map<String, HighlightField> hmap = searchHit.getHighlightFields();
                    chunk.add(createEsDoc(smap, hmap));
                }
                AggregatedPage<T> result = new AggregatedPageImpl<T>((List<T>) chunk, pageable, response.getHits().getTotalHits());
                return result;
            }

            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
                List<EsBlog> chunk = new ArrayList<>();
                // 普通查询结果
                Map<String, Object> smap = searchHit.getSourceAsMap();
                // 高亮查询结果
                Map<String, HighlightField> hmap = searchHit.getHighlightFields();
                chunk.add(createEsDoc(smap, hmap));
                return null;
            }
        });

        esBlogs.forEach(e -> logger.debug(e.toString()));
        return esBlogs;
    }

高亮效果:

注意:本项目中controller有一个init方法,此处为了方便,直接从数据库获取到数据放入es中,如果不需要可以删除,实际情况应该使用定时任务或者logstash等同步工具同步数据。

源码:https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-elasticsearch

猜你喜欢

转载自blog.csdn.net/u014553029/article/details/110506316