elasticsearch学习笔记-实战篇-spring-boot整合

本demo实现功能如下:

1、保存索引数据

2、根据ID获取索引数据

3、分页查询所有索引数据:精确匹配、时间范围查询、分词查询、高亮结果

4、利用滚动查询所有数据

pom.xml

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

application.yml

spring:
  data:
    elasticsearch:
      cluster-name: es-cluster
      cluster-nodes: localhost:9300

Message.java


@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Document(indexName = "message")
public class Message {

    @Id
    @Field(type = FieldType.Integer)
    private Integer id;

    /**
     * 类型
     */
    @Field(type = FieldType.Keyword)
    private String type;
    /**
     * 标题
     */
    @Field(type = FieldType.Text, fielddata = true, searchAnalyzer = "ik_smart", analyzer = "ik_smart")
    private String title;
    /**
     * 内容
     */
    @Field(type = FieldType.Text, fielddata = true, searchAnalyzer = "ik_smart", analyzer = "ik_smart")
    private String content;

    /**
     * 创建时间
     */
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
    private Date createTime;

}

MessageRepository.java

@Repository
public interface MessageRepository extends ElasticsearchRepository<Message, Integer> {

}

MessageService.java

public interface MessageService {

    /**
     * 查询详情
     * @param id
     * @return
     */
    Message detail(Integer id);

    /**
     * 分页查询
     * @param ao
     * @return
     */
    Page<Message> list(MessageAo ao);

    /**
     * 查询所有
     * @param ao
     * @return
     */
    List<Message> listAll(MessageAo ao);
}

MessageServiceImpl.java


@Service
public class MessageServiceImpl implements MessageService {
    @Autowired
    private MessageRepository platformBidRepository;
    @Autowired
    private ElasticSearchUtils elasticSearchUtils;

    @Override
    public Message detail(Integer id) {
        return platformBidRepository.findById(id).orElse(new Message());
    }

    @Override
    public Page<Message> list(MessageAo ao) {
        SearchQuery searchQuery = getSearchQuery(ao);
        // content返回高亮摘要
        Page<Message> page = elasticSearchUtils.queryForPage(searchQuery, Message.class,
                elasticSearchUtils.createSearchResultHighlightMapper("title","content"));
        return page;
    }

    private SearchQuery getSearchQuery(MessageAo ao) {
        // 分页参数
        Pageable pageable = PageRequest.of(ao.getPage()-1, ao.getSize());

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(ao.getType())) {
            // termQuery查询中文,需要加上.keyword,不然查询不到
            queryBuilder.must(QueryBuilders.termQuery("type.keyword", ao.getType()));
        }
        if (ao.getStartTime()!=null) {
            queryBuilder.must(QueryBuilders.rangeQuery("createTime").gte(ao.getStartTime().getTime()));
        }
        if (ao.getEndTime()!=null) {
            Date endTime = DateUtils.addDays(ao.getEndTime(), 1);
            queryBuilder.must(QueryBuilders.rangeQuery("createTime").lte(endTime.getTime()));
        }
        if (StringUtils.isNotBlank(ao.getKeyword())) {
            queryBuilder.must(QueryBuilders.matchQuery("title", ao.getKeyword()));
        }
        if (StringUtils.isNotBlank(ao.getKeyword())) {
            queryBuilder.must(QueryBuilders.matchQuery("content", ao.getKeyword()));
        }

        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                // 高亮字段
                .withHighlightFields(new HighlightBuilder.Field("title"), new HighlightBuilder.Field("content"))
                // 返回部分字段
//                .withFields("id","title","content","createTime")
                .withPageable(pageable)
                .build();
        // 有关键字按关键词得分排序,没有则按创建日期排序
        if (StringUtils.isBlank(ao.getKeyword())) {
            searchQuery.addSort(new Sort(Sort.Direction.DESC, "createTime"));
        }
        return searchQuery;
    }

    @Override
    public List<Message> listAll(MessageAo ao) {
        // 构建查询语句
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // TODO 补全查询条件
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                // 返回部分字段
//                .withFields("id","title","content","createTime")
                .withPageable(PageRequest.of(0, 10))
                .build();
        return elasticSearchUtils.queryAllByScroll(searchQuery, Message.class);
    }

}
ElasticSearchUtils.java

@Component
@Slf4j
public class ElasticSearchUtils {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    @Autowired
    private RestClientProperties restClientProperties;
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 滚动获取所有数据
     * @param searchQuery
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> queryAllByScroll(SearchQuery searchQuery, Class<T> clazz) {
        List<T> result = new ArrayList<>(1000);
        long scrollTimeInMillis = 10 * 1000;
        ScrolledPage<T> page = (ScrolledPage<T>) elasticsearchTemplate.startScroll(scrollTimeInMillis, searchQuery, clazz);
        result.addAll(page.getContent());
        while (page.hasContent()) {
            page = (ScrolledPage<T>) elasticsearchTemplate.continueScroll(page.getScrollId(), scrollTimeInMillis, clazz);
            result.addAll(page.getContent());
        }
        elasticsearchTemplate.clearScroll(page.getScrollId());
        return result;
    }

    /**
     * 分页查询
     * @param searchQuery
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Page<T> queryForPage(SearchQuery searchQuery, Class<T> clazz) {
        return elasticsearchTemplate.queryForPage(searchQuery, clazz);
    }

    public <T> Page<T> queryForPage(SearchQuery searchQuery, Class<T> clazz, SearchResultMapper searchResultMapper) {
        return elasticsearchTemplate.queryForPage(searchQuery, clazz,
                searchResultMapper);
    }

    public <T> List<T> queryForList(SearchQuery searchQuery, Class<T> clazz) {
        return elasticsearchTemplate.queryForList(searchQuery, clazz);
    }

    /**
     * 高亮结果
     */
    public SearchResultMapper createSearchResultHighlightMapper(String... fields) {
        return new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> result = new ArrayList<>();
                for (SearchHit searchHit : response.getHits()) {
                    if (response.getHits().getHits().length <= 0) {
                        break;
                    }
                    T t = JSON.parseObject(searchHit.getSourceAsString(), clazz);
                    // 内容高亮摘要
                    for (String field : fields) {
                        reWriteHighlightField(searchHit.getHighlightFields(), field, t);
                    }
                    result.add(t);
                }
                return new AggregatedPageImpl<T>(result, pageable, response.getHits().getTotalHits(), response.getAggregations(), response.getScrollId());
            }
        };
    }

    /**
     * 内容高亮摘要
     *
     * @param highlightFields
     * @param field
     * @param t
     * @param <T>
     */
    private <T> void reWriteHighlightField(Map<String, HighlightField> highlightFields, String field, T t) {
        // 内容高亮摘要
        HighlightField highlightField = highlightFields.get(field);
        if (highlightField != null) {
            // 每段摘要之间...分隔
            String value = StringUtils.join(Arrays.asList(highlightFields.get(field).fragments()), "...");
            try {
                FieldUtils.writeDeclaredField(t, field, value, true);
            } catch (Exception e) {
                log.error("【ES高亮】类{}字段{}写入异常", t.getClass(), field, e);
            }
        }
    }

    /**
     * 获取分词列表
     *
     * @param keyword
     * @return
     */
    public List<String> getAnalyzerResult(String keyword) {
        String uris = restClientProperties.getUris().get(0);
        Map<String, String> request = new HashMap<>();
        request.put("analyzer", "ik_smart");
        request.put("text", keyword);
        // 调用ES分词获取分词
        String response = restTemplate.postForObject(uris + "/_analyze", request, String.class);
        JSONArray tokens = JSON.parseObject(response).getJSONArray("tokens");
        List<String> result = new ArrayList<>();
        for (int i = 0; i < tokens.size(); i++) {
            JSONObject jsonObject = tokens.getJSONObject(i);
            String token = jsonObject.getString("token");
            result.add(token);
        }
        return result;
    }
}
MessageRepositoryTest.java
public class MessageRepositoryTest extends BasicTest {

    @Autowired
    private MessageRepository platformBidRepository;

    @Test
    public void saveTest() {
        for (int i = 0; i < 100; i++) {
            Message bean = Message.builder()
                    .id(i)
                    .type("新闻事件")
                    .title("WHO:全球累计确诊新冠肺炎病例超11万 除中国外韩国最多")
                    .content("据美国消费者新闻与商业频道(CNBC)网站报道,世界卫生组织(WHO)官员9日表示,随着新型冠状病毒肺炎疫情在全球范围的迅速蔓延,出现全球性流行病的威胁正在上升。报道称,尽管新冠肺炎疫情在中国的蔓延速度正在放缓,但在世界其他地区正在加速扩散,并已传播到100多个国家,全球累计确诊病例逾11.1万。美国约翰斯·霍普金斯大学的数据显示,韩国是中国以外确诊病例最多的国家,近7500例;紧随其后的是意大利和伊朗,截至9日上午,这两个国家均有7000多确诊病例;在美国,截至美东时间3月8日晚7点,全美共报告新冠肺炎确诊病例572例,分布在至少30个州。")
                    .createTime(new Date())
                    .build();
            platformBidRepository.save(bean);
        }
    }
}
MessageServiceImplTest.java

public class MessageServiceImplTest extends BasicTest {

    @Autowired
    private MessageServiceImpl messageService;

    @Test
    public void detail() throws Exception{
        Message message = messageService.detail(1);
        System.out.println(new ObjectMapper().writeValueAsString(message));
    }

    @Test
    public void list()  throws Exception{
        MessageAo messageAo = MessageAo.builder()
                .type("新闻事件")
                .startTime(DateUtils.parseDate("2020-03-20","yyyy-MM-dd"))
                .endTime(DateUtils.parseDate("2020-03-21","yyyy-MM-dd"))
                .keyword("WHO")
                .build();
        Page<Message> messages = messageService.list(messageAo);
        System.out.println(new ObjectMapper().writeValueAsString(messages));
    }


    @Test
    public void listAll()  throws Exception{
        MessageAo messageAo = MessageAo.builder()
                .build();
        List<Message> messages = messageService.listAll(messageAo);
        System.out.println(messages.size());
    }
}

拓展部分,摘抄自官方文档

2.2.3. Using @Query Annotation
Example 54. Declare query at the method using the @Query annotation.


public interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
    Page<Book> findByName(String name,Pageable pageable);}

Table 2. Supported keywords inside method names

Keyword

Sample

Elasticsearch Query String

And

findByNameAndPrice

{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Or

findByNameOrPrice

{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Is

findByName

{"bool" : {"must" : {"field" : {"name" : "?"}}}}

Not

findByNameNot

{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}

Between

findByPriceBetween

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

LessThanEqual

findByPriceLessThan

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

GreaterThanEqual

findByPriceGreaterThan

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Before

findByPriceBefore

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

After

findByPriceAfter

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Like

findByNameLike

{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}

StartingWith

findByNameStartingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}

EndingWith

findByNameEndingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}

Contains/Containing

findByNameContaining

{"bool" : {"must" : {"field" : {"name" : {"query" : "?","analyze_wildcard" : true}}}}}

In

findByNameIn(Collection<String>names)

{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}

NotIn

findByNameNotIn(Collection<String>names)

{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}

Near

findByStoreNear

Not Supported Yet !

True

findByAvailableTrue

{"bool" : {"must" : {"field" : {"available" : true}}}}

False

findByAvailableFalse

{"bool" : {"must" : {"field" : {"available" : false}}}}

OrderBy

findByAvailableTrueOrderByNameDesc

{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

本文demo下载:https://download.csdn.net/download/kuyuyingzi/12261433

参考资料:

https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#reference

https://blog.csdn.net/lisongjia123/article/details/79041402
https://www.cnblogs.com/sxdcgaq8080/p/10411423.html
https://my.oschina.net/chuibilong/blog/1830382
https://blog.csdn.net/garvey_wong/article/details/86624016

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值