一:需求:搜索一本书的题目,搜索关键字高亮加粗显示,(根据title中的文字进行全文搜索,展示出结果来,统计搜索时间了,返回记录数).
SpringBoot 2.0.5 RELEASE ,ElasticSearch 6.4.1,Bootstrap,Thymeleaf 3.0.9 RELEASE, Maven 3.3.9,lombok,IDEA热部署. ES的测试数据.搭建了一主两备简单集群.集群名:elasticsearch.
server:
port: 8082
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
3.0.9.RELEASE
2.1.1
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-data-elasticsearch
org.elasticsearch.plugin
transport-netty3-client
5.6.10
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.projectlombok
lombok
1.16.18
org.springframework.boot
spring-boot-devtools
true
org.springframework.boot
spring-boot-starter-test
test
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetAddress;
@Configuration
public class ElasticSearchConfig {
private static final Logger logger = LoggerFactory.getLogger(ElasticSearchConfig.class);
/** ES的配置信息*/
private static final String CLUSTER_NAME="elasticsearch";
private static final String HOST_NAME="localhost";
private static final Integer PORT=9300;
/** 返回TransportClient*/
@Bean
public TransportClient client() {
logger.info("初始化开始中...");
TransportClient client = null;
try {
TransportAddress transportAddress = new InetSocketTransportAddress(InetAddress.getByName(HOST_NAME),PORT);
// 配置信息
Settings esSetting = Settings.builder()
.put("cluster.name", CLUSTER_NAME)
.build();
// 配置信息Settings自定义
client= new PreBuiltTransportClient(esSetting);
client.addTransportAddresses(transportAddress);
} catch (Exception e) {
logger.error("elasticsearch TransportClient create error!!!", e);
}
return client;
}
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import java.util.Date;
@Document(indexName = "book",type="novel")
@Setter
@Getter
public class Novel {
@Id
private String id;
private String title;
private String author;
private Integer word_count;
private Date publish_data;
public Novel(){
super();
}
@Override
public String toString() {
return "Novel{" +
"id=" + id +
", title='" + title + '\'' +
", author='" + author + '\'' +
", word_count=" + word_count +
", publish_data=" + publish_data +
'}';
}
}
import com.lx.search.elastic.entity.Novel;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
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.springframework.beans.factory.annotation.Autowired;
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.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
@Controller
@RequestMapping("/novel")
public class NovelController {
private static final String PATH="searchtitle";
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Autowired
private TransportClient client;
/**
* 搜索Title并且高亮显示
*/
@RequestMapping("/search/title/{keyword}")
public ModelAndView searchTitle(@PathVariable String keyword) {
ModelAndView modelAndView=new ModelAndView();
// 统计查询时间,这里开始
Instant start = Instant.now();
// 构造查询条件,使用标准分词器.
QueryBuilder matchQuery = QueryBuilders.matchQuery("title", keyword).analyzer("standard").operator(Operator.OR);
// 设置高亮,使用默认的highlighter高亮器
HighlightBuilder highlightBuilder = new HighlightBuilder()
.field("title")
.preTags("")
.postTags("");
// 设置查询字段
SearchResponse response = client.prepareSearch("book")
.setQuery(matchQuery)
.highlighter(highlightBuilder)
// 设置一次返回的文档数量
.setSize(10)
.get();
// 返回搜索结果
SearchHits hits = response.getHits();
// 统计搜索结束时间
Instant end = Instant.now();
System.out.println("共搜索到: "+hits.getTotalHits() + " 条结果" + "," + "共耗时: " +Duration.between(start, end).toMillis()/1000 + " 秒");
List
【小说标题关键字搜索】
书籍名称关键字搜索
序号
Index
Type
ID
Title
Score
注意点如下:
① :如何model域中的属性值有HTML标签,使用th:utext解析即可.
② :如何获取遍历列表的序号,方式一:${stat.count}是从1开始的,方式 二:${stat.index}是从0开始的,如果从1开始就${stat.index+1}.
③ :Thymeleaf遍历Map集合${novel['添加的对象名']}.
④ :关键字高亮使用了ES的高亮器+Html标签+Thymeleaf解析含有Html标签的th:utext实现功能的.
8.1 分词器问题.
使用的是standard分词器.就是默认按照中文一个字一个字的切分,使用的是全文搜索(),可以指定分词器,
这里如何使用ik_max_word和ik_smart均不符合这里的场景的.
// 构造查询条件
QueryBuilder matchQuery = QueryBuilders.matchQuery("title", keyword).analyzer("standard").operator(Operator.OR);
matchQuery会对查询语句进行分词,分词后查询语句中任何一个词项被匹配,文档就被搜索到了,如果想查询匹配所有关键字的文档就使用AND条件连接.只匹配一个的就使用OR.(多词查询使用).
这篇文章介绍比较好.match查询是如何使用bool查询的
使用 Operator.OR (下面两个条件只要匹配了其中一个就可以搜索到文档了).
搜索关键字:mybatis 云飞
使用 Operator.AND(下面两个条件都要匹配,才能搜索到文档).
搜索关键字:mybatis 云飞
8.2 高亮器.
ES提供了三种高亮器,默认的是highlighter高亮器,postings-highlighter高亮器,fast-vector-highlighter高亮器.默认的highlighter高亮器对保存的原始文档进行二次分析,速度最慢,但是不需要额外的存储空间.