高级客户端
参考:尚硅谷网课以及笔记
Java REST Client 有两种风格:
-
Java Low Level REST Client :用于Elasticsearch的官方低级客户端。它允许通过HTTP与Elasticsearch集群通信。将请求编排和响应反编排留给用户自己处理。它兼容所有的Elasticsearch版本。(PS:学过WebService的话,对编排与反
编排这个概念应该不陌生。可以理解为对请求参数的封装,以及对响应结果的解析)
-
Java High Level REST Client :用于Elasticsearch的官方高级客户端。它是基于低级客户端的,它提供很多API,并负责请求的编排与响应的反编排。(PS:就好比是,一个是传自己拼接好的字符串,并且自己解析返回的结果;而另一个
是传对象,返回的结果也已经封装好了,直接是对象,更加规范了参数的名称以及格式,更加面对对象一点)
在 Elasticsearch 7.0 中不建议使用TransportClient,并且在8.0中会完全删除TransportClient。因此,官方更建议我们用Java High Level REST Client。
Java 高级客户端的官方文档位置:http://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html
搭建环境
-
创建项目 elasticsearch-demo
-
导入pom文件
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId> </dependency><!--引入es的坐标--><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.8.0</version></dependency><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-client</artifactId><version>7.8.0</version></dependency><dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.8.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.4</version></dependency></dependencies>
-
在 resource 文件夹下面创建 application.yml 文件
elasticsearch:host: 127.0.0.1port: 9200
-
创建启动类
package com.atguigu;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ElasticsearchDemoApplication {public static void main(String[] args) {SpringApplication.run(ElasticsearchDemoApplication.class, args);}
}
- 创建 com.atguigu.config.ElasticSearchConfig
package com.atguigu.config;import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {private String host;private int port;public String getHost() {return host;}public void setHost(String host) {this.host = host;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}@Beanpublic RestHighLevelClient client(){return new RestHighLevelClient(RestClient.builder(new HttpHost(host,port,"http")));}
}
- 新建测试类
package com.atguigu.test;import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.*;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.After;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@SpringBootTest
public class ElasticsearchTest {@Autowiredprivate RestHighLevelClient client;@Testpublic void contextLoads() {System.out.println(client);
}
}
索引操作
- 创建索引
@RunWith(SpringRunner.class)
@SpringBootTest
public class ElasticsearchTest {@Autowiredprivate RestHighLevelClient client;/** 添加索引 */@Testpublic void addIndex() throws Exception {//1.使用client获取操作索引的对象IndicesClient indicesClient = client.indices();//2.具体操作,获取返回值CreateIndexRequest createRequest = new CreateIndexRequest("abc");CreateIndexResponse response = indicesClient.create(createRequest, RequestOptions.DEFAULT);//3.根据返回值判断结果System.out.println(response.isAcknowledged());}
}
- 添加索引和映射
kibana
PUT aaa/_mapping
{"properties":{"address":{"type":"text","analyzer":"ik_max_word"},"age":{"type":"long"},"name":{"type":"keyword"}}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class ElasticsearchTest {@Autowiredprivate RestHighLevelClient client;/**添加索引带映射 */@Testpublic void addIndexAndMapping() throws IOException {//1.使用client获取操作索引的对象IndicesClient indicesClient = client.indices();//2.具体操作,获取返回值CreateIndexRequest createRequest = new CreateIndexRequest("aaa");//2.1 设置mappingsString mapping = "{\n" +" \"properties\" : {\n" +" \"address\" : {\n" +" \"type\" : \"text\",\n" +" \"analyzer\" : \"ik_max_word\"\n" +" },\n" +" \"age\" : {\n" +" \"type\" : \"long\"\n" +" },\n" +" \"name\" : {\n" +" \"type\" : \"keyword\"\n" +" }\n" +" }\n" +" }";createRequest.mapping(mapping,XContentType.JSON);CreateIndexResponse response = indicesClient.create(createRequest, RequestOptions.DEFAULT);//3.根据返回值判断结果System.out.println(response.isAcknowledged());}
}
- 判断索引是否存在
/*** 判断索引是否存在*/@Testpublic void existIndex() throws IOException {IndicesClient indices = client.indices();GetIndexRequest getRequest = new GetIndexRequest("aaa");boolean exists = indices.exists(getRequest, RequestOptions.DEFAULT);System.out.println(exists);}
- 查询索引
/*** 查询索引*/@Testpublic void queryIndex() throws IOException {IndicesClient indices = client.indices();GetIndexRequest getReqeust = new GetIndexRequest("aaa");GetIndexResponse response = indices.get(getReqeust, RequestOptions.DEFAULT);//获取结果Map<String, MappingMetaData> mappings = response.getMappings();for (String key : mappings.keySet()) {System.out.println(key+":" + mappings.get(key).getSourceAsMap());}}
- 删除索引
/*** 删除索引*/@Testpublic void deleteIndex() throws IOException {IndicesClient indices = client.indices();DeleteIndexRequest deleteRequest = new DeleteIndexRequest("abc");AcknowledgedResponse response = indices.delete(deleteRequest, RequestOptions.DEFAULT);System.out.println(response.isAcknowledged());}
文档操作
- 添加文档,使用map作为数据
/*** 添加文档,使用map作为数据*/@Testpublic void addDoc() throws IOException {//数据对象,mapMap data = new HashMap();data.put("address","hf");data.put("name","ad");data.put("age",18);//1.获取操作文档的对象IndexRequest request = new IndexRequest("aaa").id("1").source(data);//添加数据,获取结果IndexResponse response = client.index(request, RequestOptions.DEFAULT);//打印响应结果System.out.println(response.getId());}
- 添加文档,使用对象作为数据
先创建对象类
public class Person {private String id;private String name;private int age;private String address;// 设置 set get 和tostring方法
}
/**
* 添加文档,使用对象作为数据
*/
@Test
public void addDoc2() throws IOException {//数据对象,javaObjectPerson p = new Person();p.setId("2");p.setName("hf");p.setAge(20);p.setAddress("ss");//将对象转为jsonString data = JSON.toJSONString(p);//1.获取操作文档的对象IndexRequest request = new IndexRequest("aaa").id(p.getId()).source(data,XContentType.JSON);//添加数据,获取结果IndexResponse response = client.index(request, RequestOptions.DEFAULT);//打印响应结果System.out.println(response.getId());
}
- 修改文档
/*** 修改文档:添加文档时,如果id存在则修改,id不存在则添加*/
@Test
public void updateDoc() throws IOException {//数据对象,javaObjectPerson p = new Person();p.setId("2");p.setName("硅谷");p.setAge(30);p.setAddress("北京昌平区");//将对象转为jsonString data = JSON.toJSONString(p);//1.获取操作文档的对象IndexRequest request = new IndexRequest("aaa").id(p.getId()).source(data,XContentType.JSON);//添加数据,获取结果IndexResponse response = client.index(request, RequestOptions.DEFAULT);//打印响应结果System.out.println(response.getId());
}
- 根据id查询文档
/**
* 根据id查询文档
*/
@Test
public void findDocById() throws IOException {GetRequest getReqeust = new GetRequest("aaa","1");//getReqeust.id("1");GetResponse response = client.get(getReqeust, RequestOptions.DEFAULT);//获取数据对应的jsonSystem.out.println(response.getSourceAsString());
}
- 根据id删除文档
/**
* 根据id删除文档
*/
@Test
public void delDoc() throws IOException {DeleteRequest deleteRequest = new DeleteRequest("aaa","1");DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);System.out.println(response.getId());System.out.println(response.getResult());
}
- 批量操作
Bulk
批量操作是将文档的增删改查一些列操作,通过一次请求全都做完。减少网络传输次数。
这里使用的是person索引
,之前创建过,没创建的要创建。
PUT person/_mapping
{"properties":{"address":{"type":"text","analyzer":"ik_max_word"},"age":{"type":"long"},"name":{"type":"keyword"}}
}
下面java代码对应的kibana
GET person/_search# 批量操作
# 1 删除1号记录
# 2 添加8号记录
# 3 修改2号记录 名称为二号POST _bulk
{"delete":{"_index":"person","_id":"1"}}
{"create":{"_index":"person","_id":"8"}}
{"name":"8号","age":80,"address":"北京"}
{"update":{"_index":"person","_id":"2"}}
{"doc":{"name":"2号"}}
/*** 1. 批量操作 bulk*/
@Test
public void testBulk() throws IOException {//创建bulkrequest对象,整合所有操作BulkRequest bulkRequest = new BulkRequest();/*1. 删除1号记录2. 添加6号记录3. 修改3号记录 名称为 “三号”*///添加对应操作//1. 删除1号记录DeleteRequest deleteRequest = new DeleteRequest("person","1");bulkRequest.add(deleteRequest);//2. 添加6号记录Map map = new HashMap();map.put("name","六号");IndexRequest indexRequest = new IndexRequest("person").id("6").source(map);bulkRequest.add(indexRequest);Map map2 = new HashMap();map2.put("name","三号");//3. 修改3号记录 名称为 “三号”UpdateRequest updateReqeust = new UpdateRequest("person","3").doc(map2);bulkRequest.add(updateReqeust);//执行批量操作BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);RestStatus status = response.status();System.out.println(status);
}
批量导入MySQL到ES
将数据库中Goods表的数据导入到ElasticSearch中,自己的表也可以的。
① 创建Goods表并导入数据
· title:商品标题
· price:商品价格
· createTime:创建时间
· categoryName:分类名称。如:家电,手机
· brandName:品牌名称。如:华为,小米
· spec: 商品规格。如: spec:{“屏幕尺寸”,“5寸”,“内存大小”,“128G”}
· saleNum:销量
· stock:库存量
② 创建索引
PUT goods
{"mappings": {"properties": {"title": {"type": "text","analyzer": "ik_smart"},"price": { "type": "double"},"createTime": {"type": "date"},"categoryName": { "type": "keyword"},"brandName": { "type": "keyword"}, "spec": { "type": "object"},"saleNum": { "type": "integer"}, "stock": { "type": "integer"}}}
}# 查询索引
GET goods
- 添加文档数据
使用kibana操作
POST goods/_doc/1
{"title":"小米手机","price":1000,"createTime":"2019-12-01","categoryName":"手机","brandName":"小米","saleNum":3000,"stock":10000,"spec":{"网络制式":"移动4G","屏幕尺寸":"4.5"}
}# 查询文档数据
GET goods/_search
java
- 添加mybatis mysql依赖
<!--mybatis-->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.0</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
- 添加 application.yml 配置文件
# datasource
spring:datasource:url: jdbc:mysql:///es?serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver# mybatis
mybatis:mapper-locations: classpath:/mapper/*Mapper.xmltype-aliases-package: com.jiao.domain
- 添加 javabean
public class Goods {private int id;private String title;private double price;private int stock;private int saleNum;private Date createTime;private String categoryName;private String brandName;private Map spec; //将数据库中的json串解析成map进行数据封装// @JSONField(serialize = false)//在转换JSON时,忽略该字段private String specStr;//接收数据库的信息 "{\"机身内存\":\"16G\",\"网络\":\"联通3G\"}"// 生成set get 和 toString方法
}
- 创建 dao
@Mapper
public interface GoodsMapper {public List<Goods> findAll();
}
- 在 resource 文件夹下面 创建 mapper/GoodsMapper.xml 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jiao.mapper.GoodsMapper"><select id="findAll" resultType="goods">select`id`,`title`,`price`,`stock`,`saleNum`,`createTime`,`categoryName`,`brandName`,`spec` as specStrfrom goods</select>
</mapper>
- 添加测试方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class ElasticsearchTest2 {@Autowiredprivate GoodsMapper goodsMapper;@Autowiredprivate RestHighLevelClient client;/*** 批量导入*/@Testpublic void importData() throws IOException {//1.查询所有数据,mysqlList<Goods> goodsList = goodsMapper.findAll();//System.out.println(goodsList.size());//2.bulk导入BulkRequest bulkRequest = new BulkRequest();//2.1 循环goodsList,创建IndexRequest添加数据for (Goods goods : goodsList) {//2.2 设置spec规格信息 Map的数据 specStr:{}//goods.setSpec(JSON.parseObject(goods.getSpecStr(),Map.class));String specStr = goods.getSpecStr();//将json格式字符串转为Map集合Map map = JSON.parseObject(specStr, Map.class);//设置spec mapgoods.setSpec(map);//将goods对象转换为json字符串String data = JSON.toJSONString(goods);//bean --> {}IndexRequest indexRequest = new IndexRequest("goods");indexRequest.id(goods.getId()+"").source(data, XContentType.JSON);bulkRequest.add(indexRequest);}BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);System.out.println(response.status());}
}
查询操作
- 查询所有(matchAll查询)
matchAll查询:查询所有文档
kibana
# 查询
GET goods/_search
{"query": {"match_all": {}},"from": 0,"size": 100
}
/*** 查询所有* 1. matchAll* 2. 将查询结果封装为Goods对象,装载到List中* 3. 分页。默认显示10条*/
@Test
public void testMatchAll() throws IOException {//2. 构建查询请求对象,指定查询的索引名称SearchRequest searchRequest = new SearchRequest("goods");//4. 创建查询条件构建器SearchSourceBuilderSearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//6. 查询条件QueryBuilder query = QueryBuilders.matchAllQuery();//查询所有文档//5. 指定查询条件sourceBuilder.query(query);//3. 添加查询条件构建器 SearchSourceBuildersearchRequest.source(sourceBuilder);// 8. 添加分页信息sourceBuilder.from(0);sourceBuilder.size(100);//1. 查询,获取查询结果SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);//7. 获取命中对象 SearchHitsSearchHits searchHits = searchResponse.getHits();//7.1 获取总记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();//7.2 获取Hits数据 数组SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {//获取json字符串格式的数据String sourceAsString = hit.getSourceAsString();//转为java对象Goods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
- term查询
term查询:不会对查询条件进行分词。
GET goods# term 查询
GET goods/_search
{"query": {"term": {"categoryName": {"value": "手机"}}}
}
/*** termQuery:词条查询*/
@Test
public void testTermQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();QueryBuilder query = QueryBuilders.termQuery("title","华为");//term词条查询sourceBulider.query(query);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
- 模糊查询
wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
prefix查询:前缀查询
# wildcard 查询。查询条件分词,模糊查询 华为,华,*华*
GET goods/_search
{"query": {"wildcard": {"title": {"value": "华*" }}}
}# 前缀查询
GET goods/_search
{"query": {"prefix": {"brandName": {"value": "三"}}}
}
/*** 模糊查询:WildcardQuery*/
@Test
public void testWildcardQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();WildcardQueryBuilder query = QueryBuilders.wildcardQuery("title", "华*");sourceBulider.query(query);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
/**
* 模糊查询:perfixQuery
*/
@Test
public void testPrefixQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();PrefixQueryBuilder query = QueryBuilders.prefixQuery("brandName", "三");sourceBulider.query(query);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
- 范围查询
range 范围查询:查找指定字段在指定范围内包含值
# 范围查询 gte 大于等于 lte小于等于
GET goods/_search
{"query": {"range": {"price": {"gte": 2000,"lte": 3000}}}
}# 范围查询 gte 大于等于 lte小于等于
GET goods/_search
{"query": {"range": {"price": {"gte": 2000,"lte": 3000}}},"sort": [{"price": {"order": "desc"}}]
}
/*** 1. 范围查询:rangeQuery* 2. 排序*/
@Test
public void testRangeQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();//范围查询RangeQueryBuilder query = QueryBuilders.rangeQuery("price");//指定下限 gte大于等于query.gte(2000);//指定上限 小于等于query.lte(3000);sourceBulider.query(query);//排序sourceBulider.sort("price", SortOrder.DESC);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
- queryString查询
queryString:
• 会对查询条件进行分词。
• 然后将分词后的查询条件和词条进行等值匹配
• 默认取并集(OR)
• 可以指定多个查询字段
# queryString
GET goods/_search
{"query": {"query_string": {"fields": ["title","categoryName","brandName"],"query": "华为手机"}}
}
/**
* queryString
*/
@Test
public void testQueryStringQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();//queryStringQueryStringQueryBuilder query = QueryBuilders.queryStringQuery("华为手机").field("title").field("categoryName").field("brandName").defaultOperator(Operator.AND);sourceBulider.query(query);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
- 布尔查询
boolQuery:对多个查询条件连接。连接方式:
• must(and):条件必须成立
• must_not(not):条件必须不成立
• should(or):条件可以成立
• filter:条件必须成立,性能比must高。不会计算得分
# 计算得分
GET goods/_search
{"query": {"bool": {"must": [{"term": {"brandName": {"value": "华为"}}}]}}
}# 不计算得分
GET goods/_search
{"query": {"bool": {"filter": [{"term": {"brandName": {"value": "华为"}}} ]}}
}# 计算得分 品牌是三星,标题还得电视
GET goods/_search
{"query": {"bool": {"must": [{"term": {"brandName": {"value": "三星"}}}],"filter": {"term": {"title": "电视"}}}}
}
/*** 布尔查询:boolQuery* 1. 查询品牌名称为:华为* 2. 查询标题包含:手机* 3. 查询价格在:2000-3000*/
@Test
public void testBoolQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();//1.构建boolQueryBoolQueryBuilder query = QueryBuilders.boolQuery();//2.构建各个查询条件//2.1 查询品牌名称为:华为QueryBuilder termQuery = QueryBuilders.termQuery("brandName","华为");query.must(termQuery);//2.2. 查询标题包含:手机QueryBuilder matchQuery = QueryBuilders.matchQuery("title","手机");query.filter(matchQuery);//2.3 查询价格在:2000-3000QueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");((RangeQueryBuilder) rangeQuery).gte(2000);((RangeQueryBuilder) rangeQuery).lte(3000);query.filter(rangeQuery);//3.使用boolQuery连接sourceBulider.query(query);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
- 聚合查询
- 指标聚合:相当于MySQL的聚合函数。max、min、avg、sum等
- 桶聚合:相当于MySQL的 group by 操作。
不要对text类型的数据进行分组,会失败。
# 查询最贵的华为手机,max_price命名随便取,取一个有意义的名字
GET goods/_search
{"query": {"match": {"title": "华为手机"}},"aggs": {"max_price":{"max": {"field": "price"}}}
}# 桶聚合 分组
GET goods/_search
{"query": {"match": {"title": "电视"}},"aggs": {"goods_brands": {"terms": {"field": "brandName","size": 100}}}
}
/*** 聚合查询:桶聚合,分组查询* 1. 查询title包含手机的数据* 2. 查询品牌列表*/
@Test
public void testAggQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();// 1. 查询title包含手机的数据MatchQueryBuilder query = QueryBuilders.matchQuery("title", "手机");sourceBulider.query(query);// 2. 查询品牌列表/* 参数:1. 自定义的名称,将来用于获取数据2. 分组的字段*/AggregationBuilder agg = AggregationBuilders.terms("goods_brands").field("brandName").size(100);sourceBulider.aggregation(agg);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}// 获取聚合结果Aggregations aggregations = searchResponse.getAggregations();Map<String, Aggregation> aggregationMap = aggregations.asMap();//System.out.println(aggregationMap);Terms goods_brands = (Terms) aggregationMap.get("goods_brands");List<? extends Terms.Bucket> buckets = goods_brands.getBuckets();List brands = new ArrayList();for (Terms.Bucket bucket : buckets) {Object key = bucket.getKey();brands.add(key);}for (Object brand : brands) {System.out.println(brand);}
}
- 高亮查询
高亮三要素:
• 高亮字段
• 前缀
• 后缀
GET goods/_search
{"query": {"match": {"title": "电视"}},"highlight": {"fields": {"title": {"pre_tags": "<font color='red'>","post_tags": "</font>"}}}
}
/**** 高亮查询:* 1. 设置高亮* * 高亮字段* * 前缀* * 后缀* 2. 将高亮了的字段数据,替换原有数据*/
@Test
public void testHighLightQuery() throws IOException {SearchRequest searchRequest = new SearchRequest("goods");SearchSourceBuilder sourceBulider = new SearchSourceBuilder();// 1. 查询title包含手机的数据MatchQueryBuilder query = QueryBuilders.matchQuery("title", "手机");sourceBulider.query(query);//设置高亮HighlightBuilder highlighter = new HighlightBuilder();//设置三要素highlighter.field("title");highlighter.preTags("<font color='red'>");highlighter.postTags("</font>");sourceBulider.highlighter(highlighter);// 2. 查询品牌列表/*参数:1. 自定义的名称,将来用于获取数据2. 分组的字段*/AggregationBuilder agg = AggregationBuilders.terms("goods_brands").field("brandName").size(100);sourceBulider.aggregation(agg);searchRequest.source(sourceBulider);SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits searchHits = searchResponse.getHits();//获取记录数long value = searchHits.getTotalHits().value;System.out.println("总记录数:"+value);List<Goods> goodsList = new ArrayList<>();SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {String sourceAsString = hit.getSourceAsString();//转为javaGoods goods = JSON.parseObject(sourceAsString, Goods.class);// 获取高亮结果,替换goods中的titleMap<String, HighlightField> highlightFields = hit.getHighlightFields();HighlightField HighlightField = highlightFields.get("title");Text[] fragments = HighlightField.fragments();//替换goods.setTitle(fragments[0].toString());goodsList.add(goods);}for (Goods goods : goodsList) {System.out.println(goods);}
}
Spring Data Elasticsearch
· Spring Data的作用:简化了数据库的增删改查操作
Spring Data Jpa介绍
JPA是一个规范,真正操作数据库的是Hibernate(实现数据库增删改查框架[ORM框架],操作数据库采用的方式是面向对象[不写SQL语句]),而spring data jpa是对jpa的封装,将CRUD的方法封装到指定的方法中,操作的时候,只需要调用方法即可。
Spring Data Jpa的实现过程:
1:定义实体,实体类添加Jpa的注解 @Entity @Table @Cloumn @Id
2:定义接口,接口要继承JpaRepository的接口
3:配置spring容器,applicationContext.xml/SpringApplication.run(T.class,args)
Spring Data ElasticSearch简介
(1)Spring Data介绍
-
Spring Data是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。
-
其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。
-
Spring Data可以极大的简化JPA(Elasticsearch jdbc redis…)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。
-
Spring Data的官网:http://projects.spring.io/spring-data/
(2)Spring Data Elasticsearch介绍
-
Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API 进行封装 。
-
Spring Data为Elasticsearch项目提供集成搜索引擎。
-
官方网站:http://projects.spring.io/spring-data-elasticsearch/
Spring Data Elasticsearch入门
搭建工程
(1)搭建工程
创建项目 elasticsearch-springdata-es
(2)pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jiao</groupId><artifactId>elasticsearch-springdata-es</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency></dependencies><build><plugins><!-- java编译插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.2</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
</project>
(3) 编写实体类
创建com.jiao.domain.Item,代码如下:
@Document(indexName = "item",shards = 1, replicas = 1)
public class Item {@Idprivate Long id;@Field(type = FieldType.Text, analyzer = "ik_max_word")private String title; //标题@Field(type = FieldType.Keyword)private String category;// 分类@Field(type = FieldType.Keyword)private String brand; // 品牌@Field(type = FieldType.Double)private Double price; // 价格@Field(index = false, type = FieldType.Keyword)private String images; // 图片地址public Item() {}public Item(Long id, String title, String category, String brand, Double price, String images) {this.id = id;this.title = title;this.category = category;this.brand = brand;this.price = price;this.images = images;}//get/set/toString…
}
映射
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
@Document 作用在类,标记实体类为文档对象,一般有四个属性
indexName:对应索引库名称
shards:分片数量
replicas:副本数量@Id 作用在成员变量,标记一个字段作为id主键
@Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
type:字段类型,取值是枚举:FieldType
index:是否索引,布尔类型,默认是true
store:是否存储,布尔类型,默认是false
analyzer:分词器名称:ik_max_word
(4)配置 application.properties 文件
# es服务地址
elasticsearch.host=127.0.0.1
# es服务端口
elasticsearch.port=9200
# 配置日志级别,开启debug日志
logging.level.com.jiao=debug
(5)配置类
https://docs.spring.io/spring-data/elasticsearch/docs/3.2.3.RELEASE/reference/html/#elasticsearch.mapping.meta-model
https://docs.spring.io/spring-data/elasticsearch/docs/3.2.3.RELEASE/reference/html/#elasticsearch.operations.resttemplate
-
ElasticsearchRestTemplate是spring-data-elasticsearch项目中的一个类,和其他spring项目中的template类似。
-
在新版的spring-data-elasticsearch中,ElasticsearhRestTemplate代替了原来的ElasticsearchTemplate。原因是ElasticsearchTemplate基于TransportClient,TransportClient即将在8.x以后的版本中移除。所以,我们推荐使用ElasticsearchRestTemplate。
-
ElasticsearchRestTemplate基于RestHighLevelClient客户端的。需要自定义配置类,继承AbstractElasticsearchConfiguration,并实现elasticsearchClient()抽象方法,创建RestHighLevelClient对象。
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {private String host ;private Integer port ;//重写父类方法@Overridepublic RestHighLevelClient elasticsearchClient() {RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);return restHighLevelClient; }
}
测试
- 索引操作
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSpringBootES {@Autowiredprivate ElasticsearchRestTemplate elasticsearchTemplate;@Testpublic void testCreate() {// 创建索引,会根据Item类的@Document注解信息来创建elasticsearchTemplate.createIndex(Item.class); // Item是上面我们自己创建的一个实体类// 配置映射,会根据Item类中的id、Field等字段来自动完成映射elasticsearchTemplate.putMapping(Item.class);}
}
- 增删改操作
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
- 编写 ItemRepository 可以建立一个bao包存放这个接口
public interface ItemRepository extends ElasticsearchRepository<Item,Long>{
}
- 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSpringBootES {@Autowiredprivate ElasticsearchRestTemplate elasticsearchTemplate;@Autowiredprivate ItemRepository itemRepository;//增加@Testpublic void testAdd() {Item item = new Item(1L, "小米手机7", " 手机", "小米", 3499.00, "http://image.leyou.com/13123.jpg");itemRepository.save(item);}//修改(id存在就是修改,否则就是插入)@Testpublic void testUpdate() {Item item = new Item(1L, "小米手机7777", " 手机", "小米", 9499.00, "http://image.leyou.com/13123.jpg");itemRepository.save(item);}//批量新增@Testpublic void indexList() {List<Item> list = new ArrayList<>();list.add(new Item(2L, "坚果手机R1", " 手机", "锤子", 3699.00, "http://image.leyou.com/123.jpg"));list.add(new Item(3L, "华为META10", " 手机", "华为", 4499.00, "http://image.leyou.com/3.jpg"));// 接收对象集合,实现批量新增itemRepository.saveAll(list);}//删除操作@Testpublic void testDelete() {itemRepository.deleteById(1L);}//根据id查询@Testpublic void testQuery(){Optional<Item> optional = itemRepository.findById(2L);System.out.println(optional.get());}//查询全部,并按照价格降序排序@Testpublic void testFind(){// 查询全部,并按照价格降序排序Iterable<Item> items = this.itemRepository.findAll(Sort.by(Sort.Direction.DESC, "price"));items.forEach(item-> System.out.println(item));}}
自定义方法
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:
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(Collectionnames) | {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}} |
NotIn | findByNameNotIn(Collectionnames) | {“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}}}} |
例如,我们来按照价格区间查询,定义这样的一个方法:
public interface ItemRepository extends ElasticsearchRepository<Item,Long>{List<Item> findByPriceBetween(double price1, double price2);
}
然后添加一些测试数据:
@Test
public void indexList2() {List<Item> list = new ArrayList<>();list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.leyou.com/13123.jpg"));list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.leyou.com/13123.jpg"));// 接收对象集合,实现批量新增itemRepository.saveAll(list);
}
不需要写实现类,然后我们直接去运行:
@Test
public void queryByPriceBetween(){List<Item> list = this.itemRepository.findByPriceBetween(2000.00, 3500.00);for (Item item : list) {System.out.println("item = " + item);}
}
虽然基本查询和自定义方法已经很强大了,但是如果是复杂查询(模糊、通配符、词条查询等)就显得力不从心了。此时,只能使用原生查询。