Spring Boot操作ES进行各种高级查询(值得收藏)
阅读本文大概需要 15 分钟。
SpringBoot整合ES
<!-- ES 客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<!-- ES 版本 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
/**
* 在Spring容器中定义 RestClient 对象
* @Author: keats_coder
* @Date: 2019/8/9
* @Version 1.0
* */
@Configuration
public class ESConfig {
@Value("${yunshangxue.elasticsearch.hostlist}")
private String hostlist; // 127.0.0.1:9200
@Bean // 高版本客户端
public RestHighLevelClient restHighLevelClient() {
// 解析 hostlist 配置信息。假如以后有多个,则需要用 , 分开
String[] split = hostlist.split(",");
// 创建 HttpHost 数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for (int i = 0; i < split.length; i++) {
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
// 创建RestHighLevelClient客户端
return new RestHighLevelClient(RestClient.builder(httpHostArray));
}
// 项目主要使用 RestHighLevelClient,对于低级的客户端暂时不用
@Bean
public RestClient restClient() {
// 解析hostlist配置信息
String[] split = hostlist.split(",");
// 创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for (int i = 0; i < split.length; i++) {
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
return RestClient.builder(httpHostArray).build();
}
}
yunshangxue:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200}
创建操作索引的对象 构建操作索引的请求 调用对象的相关API发送请求 获取响应消息
/**
* 删除索引库
*/
@Test
public void testDelIndex() throws IOException {
// 操作索引的对象
IndicesClient indices = client.indices();
// 删除索引的请求
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("ysx_course");
// 删除索引
DeleteIndexResponse response = indices.delete(deleteIndexRequest);
// 得到响应
boolean b = response.isAcknowledged();
System.out.println(b);
}
public void testAddIndex() throws IOException {
// 操作索引的对象
IndicesClient indices = client.indices();
// 创建索引的请求
CreateIndexRequest request = new CreateIndexRequest("ysx_course");
request.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));
// 创建映射
request.mapping("doc", "{\n" +
" \"properties\": {\n" +
" \"description\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" },\n" +
" \"name\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" },\n" +
"\"pic\":{ \n" +
"\"type\":\"text\", \n" +
"\"index\":false \n" +
"}, \n" +
" \"price\": {\n" +
" \"type\": \"float\"\n" +
" },\n" +
" \"studymodel\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"timestamp\": {\n" +
" \"type\": \"date\",\n" +
" \"format\": \"yyyy-MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis\"\n" +
" }\n" +
" }\n" +
" }", XContentType.JSON);
// 执行创建操作
CreateIndexResponse response = indices.create(request);
// 得到响应
boolean b = response.isAcknowledged();
System.out.println(b);
}
Java API操作ES
准备数据环境
PUT http://localhost:9200/ysx_course/doc/_mapping
{
"properties": {
"description": { // 课程描述
"type": "text", // String text 类型
"analyzer": "ik_max_word", // 存入的分词模式:细粒度
"search_analyzer": "ik_smart" // 查询的分词模式:粗粒度
},
"name": { // 课程名称
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"pic":{ // 图片地址
"type":"text",
"index":false // 地址不用来搜索,因此不为它构建索引
},
"price": { // 价格
"type": "scaled_float", // 有比例浮点
"scaling_factor": 100 // 比例因子 100
},
"studymodel": {
"type": "keyword" // 不分词,全关键字匹配
},
"timestamp": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
POST http://localhost:9200/ysx_course/doc/1
{
"name": "Bootstrap开发",
"description": "Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
"studymodel": "201002",
"price":38.6,
"timestamp":"2018-04-25 19:11:35",
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
DSL搜索
查询全部
POST http://localhost:9200/ysx_course/doc/_search
{
"query": {
"match_all": {} // 查询全部
},
"_source" : ["name","studymodel"] // 查询结果包括 课程名 + 学习模式两个映射
}
// 搜索全部记录
@Test
public void testSearchAll() throws IOException, ParseException {
// 搜索请求对象
SearchRequest searchRequest = new SearchRequest("ysx_course");
// 指定类型
searchRequest.types("doc");
// 搜索源构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
// 设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段
searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
// 向搜索请求对象中设置搜索源
searchRequest.source(searchSourceBuilder);
// 执行搜索,向ES发起http请求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索结果
SearchHits hits = searchResponse.getHits();
// 匹配到的总记录数
long totalHits = hits.getTotalHits();
// 得到匹配度高的文档
SearchHit[] searchHits = hits.getHits();
// 日期格式化对象
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(SearchHit hit:searchHits){
// 文档的主键
String id = hit.getId();
// 源文档内容
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
// 由于前边设置了源文档字段过虑,这时description是取不到的
String description = (String) sourceAsMap.get("description");
// 学习模式
String studymodel = (String) sourceAsMap.get("studymodel");
// 价格
Double price = (Double) sourceAsMap.get("price");
// 日期
Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
System.out.println(name);
System.out.println(studymodel);
System.out.println("你看不见我,看不见我~" + description);
System.out.println(price);
}
}
坑:red>
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'yunshangxue.elasticsearch.hostlist' in value "${yunshangxue.elasticsearch.hostlist}"
分页查询
{
// from 起始索引
// size 每页显示的条数
"from" : 0, "size" : 1,
"query": {
"match_all": {}
},
"_source" : ["name","studymodel"]
}
// 搜索源构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
int page = 2; // 页码
int size = 1; // 每页显示的条数
int index = (page - 1) * size;
searchSourceBuilder.from(index);
searchSourceBuilder.size(1);
// 搜索方式
// matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
精确查询 TermQuery
{
"query": {
"term": { // 查询的方式为 term 精确查询
"name": "spring" // 查询的字段为 name 关键字是 spring
}
},
"_source": [
"name",
"studymodel"
]
}
"hits": [
{
"_index": "ysx_course",
"_type": "doc",
"_id": "3",
"_score": 0.9331132,
"_source": {
"studymodel": "201001",
"name": "spring开发基础"
}
}
]
// 搜索方式
// termQuery 精确查询
searchSourceBuilder.query(QueryBuilders.termQuery("studymodel", "201002"));
根据 ID 查询:
searchSourceBuilder.query(QueryBuilders.termQuery("_id", "1"));
全文检索 MatchQuery
{
"query": {
"match": {
"description": {
"query": "spring开发",
"operator": "or"
}
}
}
}
JavaAPI
// matchQuery全文检索
searchSourceBuilder.query(QueryBuilders.matchQuery("description", "Spring开发框架").minimumShouldMatch("70%"));
多字段联合搜索 MultiQuery
{
"query": {
"multi_match": {
"query": "Spring开发",
"minimum_should_match": "70%",
"fields": ["name", "description"]
}
}
}
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring开发框架", "name", "description").minimumShouldMatch("70%"));
提升 boost
"fields": ["name^10", "description"]
{
"_index": "ysx_course",
"_type": "doc",
"_id": "3",
"_score": 13.802518, // 可以清楚的发现,得分竟然是 13 了
"_source": {
"name": "spring开发基础",
"description": "spring 在java领域非常流行,java程序员都在用。",
"studymodel": "201001",
"price": 88.6,
"timestamp": "2018-02-24 19:11:35",
"pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
},
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring开发框架", "name", "description").field("name", 10)); // 设置 name 10倍权重
布尔查询 BoolQuery
{
"query": {
"bool": { // 布尔查询
"must": [ // 查询条件 must 表示数组中的查询方式所规定的条件都必须满足
{
"multi_match": {
"query": "spring框架",
"minimum_should_match": "50%",
"fields": [
"name^10",
"description"
]
}
},
{
"term": {
"studymodel": "201001"
}
}
]
}
}
}
// 搜索方式
// 首先构造多关键字查询条件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring开发框架", "name", "description").field("name", 10);
// 然后构造精确匹配查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("studymodel", "201002");
// 组合两个条件,组合方式为 must 全满足
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
boolQueryBuilder.must(termQueryBuilder);
// 将查询条件封装给查询对象
searchSourceBuilder.query(boolQueryBuilder);
过滤器
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "spring框架",
"minimum_should_match": "50%",
"fields": [
"name^10",
"description"
]
}
}
],
"filter": [
{
// 过滤条件:studymodel 必须是 201001
"term": {"studymodel": "201001"}
},
{
// 过滤条件:价格 >=60 <=100
"range": {"price": {"gte": 60,"lte": 100}}
}
]
}
}
}
// 首先构造多关键字查询条件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "name", "description").field("name", 10);
// 添加条件到布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
// 通过布尔查询来构造过滤查询
boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel", "201001"));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
// 将查询条件封装给查询对象
searchSourceBuilder.query(boolQueryBuilder);
排序
{
"query": {
"bool": {
"filter": [
{
"range": {
"price": {
"gte": 0,
"lte": 100
}
}
}
]
}
},
"sort": [ // 注意这里排序是写在 query key 的外面的。这就表示它的API也不是布尔查询提供
{
"studymodel": "desc" // 对 studymodel(keyword)降序
},
{
"price": "asc" // 对 price(double)升序
}
]
}
// 排序查询
@Test
public void testSort() throws IOException, ParseException {
// 搜索请求对象
SearchRequest searchRequest = new SearchRequest("ysx_course");
// 指定类型
searchRequest.types("doc");
// 搜索源构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// 添加条件到布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 通过布尔查询来构造过滤查询
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100));
// 将查询条件封装给查询对象
searchSourceBuilder.query(boolQueryBuilder);
// 向搜索请求对象中设置搜索源
searchRequest.source(searchSourceBuilder);
// 设置排序规则
searchSourceBuilder.sort("studymodel", SortOrder.DESC); // 第一排序规则
searchSourceBuilder.sort("price", SortOrder.ASC); // 第二排序规则
// 执行搜索,向ES发起http请求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索结果
SearchHits hits = searchResponse.getHits();
// 匹配到的总记录数
long totalHits = hits.getTotalHits();
// 得到匹配度高的文档
SearchHit[] searchHits = hits.getHits();
// 日期格式化对象
soutData(searchHits);
}
高亮显示
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "开发框架",
"minimum_should_match": "50%",
"fields": [
"name^10",
"description"
],
"type": "best_fields"
}
}
]
}
},
"sort": [
{
"price": "asc"
}
],
"highlight": {
"pre_tags": [
"<em>"
],
"post_tags": [
"</em>"
],
"fields": {
"name": {},
"description": {}
}
}
}
// 高亮查询
@Test
public void testHighLight() throws IOException, ParseException {
// 搜索请求对象
SearchRequest searchRequest = new SearchRequest("ysx_course");
// 指定类型
searchRequest.types("doc");
// 搜索源构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// 首先构造多关键字查询条件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "name", "description").field("name", 10);
// 添加条件到布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
// 通过布尔查询来构造过滤查询
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
// 将查询条件封装给查询对象
searchSourceBuilder.query(boolQueryBuilder);
// ***********************
// 高亮查询
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<em>"); // 高亮前缀
highlightBuilder.postTags("</em>"); // 高亮后缀
highlightBuilder.fields().add(new HighlightBuilder.Field("name")); // 高亮字段
// 添加高亮查询条件到搜索源
searchSourceBuilder.highlighter(highlightBuilder);
// ***********************
// 设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段
searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
// 向搜索请求对象中设置搜索源
searchRequest.source(searchSourceBuilder);
// 执行搜索,向ES发起http请求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索结果
SearchHits hits = searchResponse.getHits();
// 匹配到的总记录数
long totalHits = hits.getTotalHits();
// 得到匹配度高的文档
SearchHit[] searchHits = hits.getHits();
// 日期格式化对象
soutData(searchHits);
}
private void soutData(SearchHit[] searchHits) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (SearchHit hit : searchHits) {
// 文档的主键
String id = hit.getId();
// 源文档内容
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
// 获取高亮查询的内容。如果存在,则替换原来的name
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if( highlightFields != null ){
HighlightField nameField = highlightFields.get("name");
if(nameField!=null){
Text[] fragments = nameField.getFragments();
StringBuffer stringBuffer = new StringBuffer();
for (Text str : fragments) {
stringBuffer.append(str.string());
}
name = stringBuffer.toString();
}
}
// 由于前边设置了源文档字段过虑,这时description是取不到的
String description = (String) sourceAsMap.get("description");
// 学习模式
String studymodel = (String) sourceAsMap.get("studymodel");
// 价格
Double price = (Double) sourceAsMap.get("price");
// 日期
Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
System.out.println(name);
System.out.println(id);
System.out.println(studymodel);
System.out.println("你看不见我,看不见我~" + description);
System.out.println(price);
}
}
推荐阅读:
又涨了!2021 年 5 月程序员工资统计新鲜出炉,网友:还是Java程序员牛逼~
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。