牛客网后端项目实战(三十五):Spring整合Elasticsearch

Elasticsearch安装

1、配置

#集群名
cluster.name: nowcoder
#路径
path.data: D:\JavaWork\data\elasticsearch-6.4.3\data
path.logs: D:\JavaWork\data\elasticsearch-6.4.3\logs

Spring整合Elasticsearch

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

引入依赖

1、pom.xml

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>

这里删去了版本。因为POM文件是继承了父POM,父POM里面的版本是已经测试过的不会产生冲突的。

2、配置

# ElasticsearchProperties
spring.data.elasticsearch.cluster-name=nowcoder    # 集群名称
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300   # 设置节点

3、解决netty冲突问题

    @PostConstruct
    public void init() {
        // 解决netty启动冲突问题
        // see Netty4Utils.setAvailableProcessors()
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }

该注解表明是在构造器后调用,即初始化方法。
设置参数es.set.netty.runtime.available.processors 是因为,Redis和ElasticsearchProperties 底层都基于netty,同时加载的时候ElasticsearchProperties会有点小dug。

使用

在实体类中添加注解

对于要搜索的实体(这里是帖子),必须要将数据放在Es服务器里才能搜索。因此要配置数据库中的表、字段,与Es中的索引、类型等的对应关系。
Elasticsearch常用注解:https://blog.csdn.net/qq_41345773/article/details/105618712

import java.util.Date;

@Document(indexName = "discusspost", type = "_doc", shards = 6, replicas = 3)
public class DiscussPost {

    @Id
    private int id;

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

    // 互联网校招
    @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、@id 表示是文档的id,文档可以认为是mysql中表行的概念
2、@Field(type = FieldType.Text, analyzer = “ik_max_word”, searchAnalyzer = “ik_smart”)
type参数是文档中字段的类型,FieldType.Text 会进行分词并建了索引的字符类型;analyzer = “ik_max_word” 指定储存时的解析器,尽可能多的进行分词;searchAnalyzer = “ik_smart” 搜索时使用的解析器。

controller

@Repository // 数据库注解
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {  // DiscussPost要搜索的实体类,Integer声明主键类型

}

增删改

在test包里进行测试
1、增加

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

    @Autowired
    private DiscussPostMapper discussMapper;

    @Autowired
    private DiscussPostRepository discussRepository;

    @Autowired
    private ElasticsearchTemplate elasticTemplate;

	// 一次插入一条数据
    @Test
    public void testInsert() {
        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));
    }

2、修改

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

3、删除

    @Test
    public void testDelete() {
        discussRepository.deleteById(231); // 删除一条数据
        discussRepository.deleteAll();          // 删除整个索引
    }

总结下来就是,先从数据库中查找数据,再使用ElasticsearchRepository的接口中的方法调用相关API

搜索数据

1、使用Repository,最后返回结果封装到Page里。该方法中 底层获取得到了高亮显示的值, 但是没有返回

    @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>"),
                        new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
                ).build();

        // elasticTemplate.queryForPage(searchQuery, class, SearchResultMapper)
        // 底层获取得到了高亮显示的值, 但是没有返回.

        Page<DiscussPost> page = discussRepository.search(searchQuery); // 返回的结果由Page封装
        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);
        }
    }

2、使用Template 将搜索结果高亮显示

    @Test
    public void testSearchByTemplate() {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content")) // 多字段搜索
                .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC)) // 按照type(是否加精)、分数、创建时间排序
                .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
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
                SearchHits hits = response.getHits();  // 获取命中的数据
                if (hits.getTotalHits() <= 0) {
                    return null;
                }

				// 将命中的数据进行处理,包装成Discusspost 放入list内
                List<DiscussPost> list = new ArrayList<>();
                for (SearchHit hit : hits) {
                	// 将命中的数据包装成Discusspost
                    DiscussPost post = new DiscussPost();

                    String id = hit.getSourceAsMap().get("id").toString(); // 先获取数据再设置·Discusspost类
                    post.setId(Integer.valueOf(id));

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

                    String title = hit.getSourceAsMap().get("title").toString();
                    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());
                    }

                    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());
            }
        });

        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
1、获取命中的数据
2、将命中的数据进行处理,包装成Discusspost 放入list内
a)高亮显示的结果
HighlightField titleField = hit.getHighlightFields().get(“title”); 得到一个字段
titleField.getFragments()[0] 返回一个数组,取第一段,重新放入实体中
3、最后返回 new AggregatedPageImpl

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值