5-7:Spring整合Elasticsearch

  • 引入依赖
    • spring-boot-starter-data-elasticsearch
      配置Elasticsearch
    • cluster-name、cluster-nodes.
  • Spring Data Elasticsearch
    • ElasticsearchTemplate
    • ElasticsearchRepository

实现对帖子的搜索,将帖子的数据存到ES中

EslaticSearch使用

  1. 下载依赖
    在这里插入图片描述
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

  1. 配置文件
    在application.properties追加配置
# ElasticsearchProperties
spring.data.elasticsearch.cluster-name=nowcoder
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
(节点的ip地址和端口,es有两个端口,9200是http访问端口,9300是tcp访问)
  1. 报错情况
    在这里插入图片描述
    在这里插入图片描述
    因此还需要再配置
    在这里插入图片描述
  2. CommunityApplication.java
package com.nowcoder.community;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PostConstruct;

@SpringBootApplication
public class CommunityApplication {

    @PostConstruct//管理bean的生命周期,构造器调用完后执行,一般是初始化时,且这个bean是最先被初始化的。
    public void init() {
        // 解决netty启动冲突问题
        // see Netty4Utils.setAvailableProcessors()
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }

    public static void main(String[] args) {
        SpringApplication.run(CommunityApplication.class, args);
    }

}

  1. 将实体类与ES索引关联起来
package com.nowcoder.community.entity;

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

import java.util.Date;
//自动把实体映射到es的索引中对应,
@Document(indexName = "discusspost", type = "_doc", shards = 6, replicas = 3)//discusspost索引的名字,shards = 6分片;replicas = 3备份
public class DiscussPost {

    @Id//设置数据库属性与索引字段的对应关系
    private int id;

    @Field(type = FieldType.Integer)//里面声明类型
    private int userId;

    // “互联网校招”,analyzer存储时的解析器,将此词条拆分,尽量拆分为多的单词;
    // searchAnalyzer搜索时的解析器,用聪明的方式去拆分,在搜索时,拆分成尽可能少,但效率更高的词
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String title;

    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String content;

    @Field(type = FieldType.Integer)
    private int type;

    @Field(type = FieldType.Integer)
    private int status;

    @Field(type = FieldType.Date)
    private Date createTime;

    @Field(type = FieldType.Integer)
    private int commentCount;

    @Field(type = FieldType.Double)
    private double score;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public int getCommentCount() {
        return commentCount;
    }

    public void setCommentCount(int commentCount) {
        this.commentCount = commentCount;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "DiscussPost{" +
                "id=" + id +
                ", userId=" + userId +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", type=" + type +
                ", status=" + status +
                ", createTime=" + createTime +
                ", commentCount=" + commentCount +
                ", score=" + score +
                '}';
    }
}

  1. 定义数据访问层的方法
    dao下专门定义一个Elastcsearch包,

@Mapper是spring中专门用来定义mybatis数据库的注解,而spring是@repository是专门提供的数据访问层的接口(默认)

package com.nowcoder.community.dao.elasticsearch;

import com.nowcoder.community.entity.DiscussPost;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {//声明实体类是谁,主键的类型
            //spring会自动创建好实现类,直接调用即可。
}

  1. 测试用法
package com.nowcoder.community;

import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.dao.elasticsearch.DiscussPostRepository;
import com.nowcoder.community.entity.DiscussPost;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class ElasticsearchTests {

    @Autowired
    private DiscussPostMapper discussMapper;//从mysql中取数据需要用到

    @Autowired
    private DiscussPostRepository discussRepository;//要在ES中存储数据

    @Autowired
    private ElasticsearchTemplate elasticTemplate;
    //有些特殊情况discussRepository无法处理,需要此对象

    @Test//在测试之前,需要检查,ES数据库中有没有对应的索引。往ES中插入数据,数据从mysql中取到
    public void testInsert() {//注意要启动elasticsearch.bat
        discussRepository.save(discussMapper.selectDiscussPostById(241));//插入单条数据
        discussRepository.save(discussMapper.selectDiscussPostById(242));
        discussRepository.save(discussMapper.selectDiscussPostById(243));
    }

    @Test
    public void testInsertList() {//插入多条数据
        discussRepository.saveAll(discussMapper.selectDiscussPosts(101, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(102, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(103, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(111, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(112, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(131, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(132, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(133, 0, 100));
        discussRepository.saveAll(discussMapper.selectDiscussPosts(134, 0, 100));
    }

    @Test
    public void testUpdate() {//修改数据
        DiscussPost post = discussMapper.selectDiscussPostById(231);
        post.setContent("我是新人,使劲灌水.");
        discussRepository.save(post);//覆盖
    }

    @Test
    public void testDelete() {
        // discussRepository.deleteById(231);
        discussRepository.deleteAll();
    }

    @Test//演示最核心的搜索功能,并且对搜索结果进行高亮显示
    public void testSearchByRepository() {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()//构造接口的实现类
                .withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))//互联网寒冬,搜索的条件;多字段的搜索,全文搜索
                .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))//构造排序的条件“置顶”,“分数”,“时间先后”
                .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .withPageable(PageRequest.of(0, 10))//按分页查询;
                .withHighlightFields(//置顶那些字段做高亮显示,可以指定多个字段中匹配的那个词高做高亮,
                        new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),//在前后加入标签,达到高亮的结果(对标签加CSS样式即可)
                        new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
                ).build();//build方法执行,实现类就会被返回

        // elasticTemplate.queryForPage(searchQuery, class, SearchResultMapper)
        // 底层获取得到了高亮显示的值, 但是没有返回.查到的数据没有将两份数据处理,
        // 一份是高亮显示的值,另一份是原始数据

        Page<DiscussPost> page = discussRepository.search(searchQuery);
        System.out.println(page.getTotalElements());//一共有多少个实体
        System.out.println(page.getTotalPages());//一共有几页
        System.out.println(page.getNumber());//当前处在第几页
        System.out.println(page.getSize());//每一页显示几条数据
        for (DiscussPost post : page) {//遍历
            System.out.println(post);
        }
    }
    // elasticTemplate.queryForPage(searchQuery, class, SearchResultMapper)
    // 底层获取得到了高亮显示的值, 但是没有返回.查到的数据没有将两份数据处理,
    // 一份是高亮显示的值,另一份是原始数据



    @Test
    public void testSearchByTemplate() {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
                .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .withPageable(PageRequest.of(0, 10))
                .withHighlightFields(
                        new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
                        new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
                ).build();

        Page<DiscussPost> page = elasticTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() {

            @Override//  解决上述问题的办法就是使用elasticTemplate做搜索;queryForPage会把得到的结果输送到new SearchResultMapper(),
            // 而这个mapper会将高亮的结果封装返回给response
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
                SearchHits hits = response.getHits();//获取搜索后得到的数据
                if (hits.getTotalHits() <= 0) {//判断返回的数据是否为空
                    return null;
                }

                List<DiscussPost> list = new ArrayList<>();
                for (SearchHit hit : hits) {//每得到一个命中的数据,都包装到实体类中去
                    //返回的是JSON数据,在这个hit对象中,将JSON数据转化成了map(key,value)
                    DiscussPost post = new DiscussPost();

                    String id = hit.getSourceAsMap().get("id").toString();//将对应的字段,转一下
                    post.setId(Integer.valueOf(id));

                    String userId = hit.getSourceAsMap().get("userId").toString();
                    post.setUserId(Integer.valueOf(userId));

                    String title = hit.getSourceAsMap().get("title").toString();
                    //获取原始的title,要先查找有高亮的帖子后,
                    // 再统一将高亮的部分设置,不能一开始就取高亮部分,因为可能没有获取到值
                    post.setTitle(title);

                    String content = hit.getSourceAsMap().get("content").toString();
                    post.setContent(content);

                    String status = hit.getSourceAsMap().get("status").toString();
                    post.setStatus(Integer.valueOf(status));

                    String createTime = hit.getSourceAsMap().get("createTime").toString();
                    post.setCreateTime(new Date(Long.valueOf(createTime)));

                    String commentCount = hit.getSourceAsMap().get("commentCount").toString();
                    post.setCommentCount(Integer.valueOf(commentCount));

                    // 处理高亮显示的结果
                    HighlightField titleField = hit.getHighlightFields().get("title");
                    if (titleField != null) {//如果有,覆盖掉原始没有高亮显示的地方
                        post.setTitle(titleField.getFragments()[0].toString());//getFragments()返回的是一个数组,只高亮第一段,可能title中有很多命中的字段
                    }

                    HighlightField contentField = hit.getHighlightFields().get("content");
                    if (contentField != null) {
                        post.setContent(contentField.getFragments()[0].toString());
                    }

                    list.add(post);
                }

                return new AggregatedPageImpl(list, pageable,
                        hits.getTotalHits(), response.getAggregations(), response.getScrollId(), hits.getMaxScore());
                /**
                 * list:命中结果集,
                 * pageable,方法的参数
                 * hits.getTotalHits()一共多少调数据,
                 * response.getAggregations(),
                 * response.getScrollId(),
                 * hits.getMaxScore()
                 * **/
                //返回的一个对象要附和后续业务场景所需数据的要求,所以要传的数据比较多
            }
        });

        System.out.println(page.getTotalElements());
        System.out.println(page.getTotalPages());
        System.out.println(page.getNumber());
        System.out.println(page.getSize());
        for (DiscussPost post : page) {
            System.out.println(post);
        }
    }

}

在这里插入图片描述

返回的结果是一小段,不是一整篇文章,是合理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值