赞
踩
在 elasticsearch 官网中提供了各种语言的客户端:
选择 Java REST Client
选择 Java High Level Rest Client 版本,这里有使用的API
<dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version> </dependency>
创建索引库的同时创建type及其映射关系,但是这些操作不建议使用java客户端完成,原因如下:
索引库和映射往往是初始化时完成,不需要频繁操作,不如提前配置好
官方提供的创建索引库及映射API非常繁琐,需要通过字符串拼接json结构:
request.mapping(
"{\n" +
" \"properties\": {\n" +
" \"message\": {\n" +
" \"type\": \"text\"\n" +
" }\n" +
" }\n" +
"}",
XContentType.JSON);
因此,这些操作建议还是使用 Rest 风格API去实现。
以这样一个商品数据为例来创建索引库:
新增实体类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
private Long id;
private String title; //标题
private String category;// 分类
private String brand; // 品牌
private Double price; // 价格
private String images; // 图片地址
}
映射配置:
PUT /item { "settings": { "number_of_shards": 3, "number_of_replicas": 1 }, "mappings": { "_doc": { "properties": { "id": { "type": "keyword" }, "title": { "type": "text", "analyzer": "ik_max_word" }, "category": { "type": "keyword" }, "brand": { "type": "keyword" }, "images": { "type": "keyword", "index": false }, "price": { "type": "double" } } } } }
查看添加结果
完成任何操作都需要通过 RestHighLevelClient 客户端
入门示例
@RunWith(SpringRunner.class) @SpringBootTest class EsDemoApplicationTests { RestHighLevelClient client; /** * 初始化连接 */ @Before void init() { //初始化:高级客户端 client = new RestHighLevelClient(RestClient.builder( new HttpHost("192.168.85.135", 9201, "http"), new HttpHost("192.168.85.135", 9202, "http"), new HttpHost("192.168.85.135", 9203, "http") )); } @After void close() throws IOException { client.close(); } }
应用级案例
application.yml 配置文件
# es集群名称 elasticsearch.clusterName=single-node-cluster # es用户名 elasticsearch.userName=elastic # es密码 elasticsearch.password=elastic # es 是否启用用户密码 elasticsearch.passwdEnabled=true # es host ip 地址(集群):本次使用的是单机模式 elasticsearch.hosts=43.142.243.124:9200 # es 请求方式 elasticsearch.scheme=http # es 连接超时时间 elasticsearch.connectTimeOut=1000 # es socket 连接超时时间 elasticsearch.socketTimeOut=30000 # es 请求超时时间 elasticsearch.connectionRequestTimeOut=500 # es 连接保持活跃时间(ms) elasticsearch.keepAliveStrategy=180000 # es 最大连接数 elasticsearch.maxConnectNum=100 # es 每个路由的最大连接数 elasticsearch.maxConnectNumPerRoute=100
es连接配置类
import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; /** * restHighLevelClient 客户端配置类 */ @Slf4j @Data @Configuration @ConfigurationProperties(prefix = "elasticsearch") public class ElasticsearchConfig { // es host ip 地址(集群) private String hosts; // es用户名 private String userName; // es密码 private String password; // es 是否启用用户密码 private boolean passwdEnabled; // es 请求方式 private String scheme; // es集群名称 private String clusterName; // es 连接超时时间 private int connectTimeOut; // es socket 连接超时时间 private int socketTimeOut; // es 请求超时时间 private int connectionRequestTimeOut; // es 连接保持活跃时间 private int keepAliveStrategy; // es 最大连接数 private int maxConnectNum; // es 每个路由的最大连接数 private int maxConnectNumPerRoute; @Bean(name = "restHighLevelClient") public RestHighLevelClient restHighLevelClient() { // 拆分地址。单节点配一个地址即可 List<HttpHost> hostLists = new ArrayList<>(); hosts = hosts.replace("http://", ""); String[] hostList = hosts.split(","); for (String addr : hostList) { String host = addr.split(":")[0]; String port = addr.split(":")[1] == null ? "9200": addr.split(":")[1]; hostLists.add(new HttpHost(host, Integer.parseInt(port), scheme)); } // 转换成 HttpHost 数组 HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{}); // 构建连接对象 RestClientBuilder builder = RestClient.builder(httpHost); // 连接延时配置 builder.setRequestConfigCallback(requestConfigBuilder -> { requestConfigBuilder .setConnectTimeout(connectTimeOut) .setSocketTimeout(socketTimeOut) .setConnectionRequestTimeout(connectionRequestTimeOut); return requestConfigBuilder; }); builder.setHttpClientConfigCallback(httpClientBuilder -> { httpClientBuilder // 连接数配置 .setMaxConnTotal(maxConnectNum) .setMaxConnPerRoute(maxConnectNumPerRoute) // 连接保持活跃时间配置 .setKeepAliveStrategy((HttpRequest, HttpResponse) -> keepAliveStrategy); // 设置用户名、密码 if (passwdEnabled) { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password)); httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } return httpClientBuilder; }); return new RestHighLevelClient(builder); } }
注:
KeepAliveStrategy :
HTTP 规范没有确定一个持久连接可能或应该保持活动多长时间。
一些HTTP服务器使用非标准的头部信息 Keep-Alive 来告诉客户端它们想在服务器端保持连接活动的周期秒数。
如果这个信息可用,HttpClient 就会利用这个它。
如果头部信息 Keep-Alive 在响应中不存在,HttpClient 假设连接无限期的保持活动。
然而许多现实中的 HTTP 服务器配置了在特定不活动周期之后丢掉持久连接来保存系统资源,往往这是不通知客户端的。
索引的 mappings
mapping_test.json
{ "properties": { "brandName": { "type": "keyword" }, "categoryName": { "type": "keyword" }, "createTime": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, "id": { "type": "long" }, "price": { "type": "double" }, "saleNum": { "type": "integer" }, "status": { "type": "integer" }, "stock": { "type": "integer" }, "spec": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" } } }
import com.example.test.service.es.IndexTestService; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.client.IndicesClient; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Map; /** * 索引服务类 */ @Service public class IndexTestServiceImpl implements IndexTestService { @value("classpath:json/mapping_test.json") private resoure mappingTest; @Autowired RestHighLevelClient restHighLevelClient; // 分片数的配置名 private String shardNumName = "number_of_shards"; // 副本数的配置名 private String replicaNumName = "number_of_replicas"; // 索引名 private String index = "goods" @Override public boolean indexCreate() throws Exception { // 1、创建 创建索引request 参数:索引名 CreateIndexRequest indexRequest = new CreateIndexRequest(index); // 2、设置索引的settings indexRequest.settings(Settings.builder().put(shardNumName, 3).put(replicaNumName, 1)); // 3、设置索引的mappings(表结构) String mappingJson = IOUtils.toString(mappingTest.getInputStream(), Charset.forName("UTF-8")); indexRequest.mapping(mappingJson, XContentType.JSON); // 4、 设置索引的别名 // 5、 发送请求 // 5.1 同步方式发送请求 // 请求服务器 IndicesClient indicesClient = restHighLevelClient.indices(); CreateIndexResponse response = indicesClient.create(indexRequest, RequestOptions.DEFAULT); return response.isAcknowledged(); } /** * 获取表结构 * GET goods/_mapping */ @Override public Map<String, Object> getMapping(String indexName) throws Exception { IndicesClient indicesClient = restHighLevelClient.indices(); // 创建get请求 GetIndexRequest request = new GetIndexRequest(indexName); // 发送get请求 GetIndexResponse response = indicesClient.get(request, RequestOptions.DEFAULT); // 获取表结构 Map<String, MappingMetaData> mappings = response.getMappings(); Map<String, Object> sourceAsMap = mappings.get(indexName).getSourceAsMap(); return sourceAsMap; } /** * 删除索引库 */ @Override public boolean indexDelete(String indexName) throws Exception { IndicesClient indicesClient = restHighLevelClient.indices(); // 创建delete请求方式 DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName); // 发送delete请求 AcknowledgedResponse response = indicesClient.delete(deleteIndexRequest, RequestOptions.DEFAULT); return response.isAcknowledged(); } /** * 判断索引库是否存在 */ @Override public boolean indexExists(String indexName) throws Exception { IndicesClient indicesClient = restHighLevelClient.indices(); // 创建get请求 GetIndexRequest request = new GetIndexRequest(indexName); // 判断索引库是否存在 boolean result = indicesClient.exists(request, RequestOptions.DEFAULT); return result; } }
文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.8/java-rest-high-document-index.html
示例:
// 新增文档 @Test void add() throws IOException { // 准备文档 Item item = new Item(1L, "小米手机9", "手机", "小米", 3499.00, "http://image.leyou.com/13123.jpg"); // 将对象转换为Json String json = JSON.toJSONString(item); // 创建索引请求 参数为: 索引库名 类型名 文档ID IndexRequest request = new IndexRequest("item", "_doc", item.getId().toString()); // 将Json格式的数据放入到请求中 request.source(json, XContentType.JSON); // 发送请求 IndexResponse response = client.index(request); // 打印结果 System.out.println("结果为:" + response); }
响应:
response = IndexResponse[index=item,type=docs,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":2,"failed":0}]
示例:
// 根据ID获取文档
@Test
void get() throws IOException {
// 创建get请求对象 参数为: 索引库名 类型名 文档ID
GetRequest request = new GetRequest("item","_doc","1");
// 发送请求
GetResponse response = client.get(request);
// 解析结果 结果为Json
String source = response.getSourceAsString();
// 将Json数据转换为对象 参数为: Json字符串 类的字节码
Item item = JSON.parseObject(source, Item.class);
// 打印结果
System.out.println(item);
}
示例:
// 根据ID更新文档 @Test void update() throws IOException{ // 准备文档 Item item = new Item(1L, "小米手机9", "手机", "小米", 3699.00, "http://image.leyou.com/13123.jpg"); // 将对象转换为Json String json = JSON.toJSONString(item); // 创建Update请求对象 参数为: 索引库名 类型名 文档ID UpdateRequest request = new UpdateRequest("item","_doc","1"); // 将Json格式的数据放入到请求中 request.doc(json,XContentType.JSON); // 发送请求 UpdateResponse response = client.update(request); // 打印结果 System.out.println("结果为:" + response); }
示例:
// 根据ID删除文档
@Test
void delete() throws IOException {
// 创建Delete请求对象 参数为: 索引库名 类型名 文档ID
DeleteRequest request = new DeleteRequest("item","_doc","1");
// 发送请求
DeleteResponse response = client.delete(request);
// 打印结果
System.out.println("结果为:" + response);
}
示例:
// 批量插入 @Test void bulkInsert() throws IOException { // 准备文档数据: 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")); // 创建批量新增请求 BulkRequest request = new BulkRequest(); // 遍历集合 for (Item item : list) { // 将索引请求添加到批量请求对象中 request.add(new IndexRequest("item", "_doc", item.getId().toString()) .source(JSON.toJSONString(item), XContentType.JSON)); } // 发送请求 BulkResponse response = client.bulk(request); // 打印结果 System.out.println("结果为:" + response); }
示例:
/** * 查询文档 */ @Test public void searchDoc() throws IOException { // 创建查询请求对象 指定查询的索引名称 SearchRequest request = new SearchRequest("item"); // 指定查询的源 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 构建查询条件 QueryBuilder query = QueryBuilders.matchAllQuery(); // 查询的是所有的文档 // 添加查询条件 sourceBuilder.query(query); // 添加查询源 request.source(sourceBuilder); // 发送请求 SearchResponse response = client.search(request); // 分析响应结果 // 返回命中的数据对象 SearchHits searchHits = response.getHits(); // 获取命中的文档个数 long totalHits = searchHits.totalHits; System.out.println("命中的文档个数为: " + totalHits); // 获取命中的数据 SearchHit[] hits = searchHits.getHits(); // 遍历数组 for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); // 转换成对象 Item item = JSON.parseObject(sourceAsString, Item.class); System.out.println(item); } }
上面的代码中,搜索条件是通过 sourceBuilder.query(QueryBuilders.matchAllQuery())来添加的。这个 query() 方法接受的参数是: QueryBuilder 接口类型。
QueryBuilder 接口提供了很多实现类,分别对应不同类型的查询,例如:term查询、match查询、range查询、boolean查询等。若要使用各种不同查询,只需传递不同的参数给 sourceBuilder.query() 方法即可。而这些实现类并不需要去 new ,官方提供了 QueryBuilders 工厂来构建各种实现类
其实搜索类型的变化,仅仅是利用QueryBuilders构建的查询对象不同而已,其他代码基本一致:
// 使用match查询,参数为 1 查询的字段 2 查询的关键字
QueryBuilder query = QueryBuilders.matchQuery("title","小米");
// 使用term查询,参数为 1 查询的字段 2 查询的关键字
QueryBuilder query = QueryBuilders.termQuery("title","小米手机");
支持下面的范围关键字:
方法 | 说明 |
---|---|
gt(Object from) | 大于 |
gte(Object from) | 大于等于 |
lt(Object from) | 小于 |
lte(Object from) | 小于等于 |
示例:
// 使用rangeQuery查询,参数为 查询的字段
QueryBuilder query = QueryBuilders.rangeQuery("price").gte(2000).lt(4000); // 参数为:查询的字段 后面是链式的调用
响应:
item = Item(id=2, title=坚果手机R1, category=手机, brand=锤子, price=3699.0,
images=http://image.leyou.com/13123.jpg)
item = Item(id=5, title=荣耀V10, category=手机, brand=华为, price=2799.0,
images=http://image.leyou.com/13123.jpg)
item = Item(id=1, title=小米手机7, category=手机, brand=小米, price=3299.0,
images=http://image.leyou.com/13123.jpg)
默认情况下,索引库中所有数据都会返回,如果想只返回部分字段,可以通过fetchSource来控制。
示例:
/** * 查询文档 */ @Test public void searchDoc() throws IOException { // 创建查询请求对象 指定查询的索引名称 SearchRequest request = new SearchRequest("item"); // 指定查询的源 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 构建查询条件 // QueryBuilder query = QueryBuilders.matchAllQuery(); // 查询的是所有的文档 // 使用match查询,参数为 1 查询的字段 2 查询的关键字 // QueryBuilder query = QueryBuilders.matchQuery("title","小米"); // 使用term查询,参数为 1 查询的字段 2 查询的关键字 // QueryBuilder query = QueryBuilders.termQuery("title","小米手机"); // 使用rangeQuery查询,参数为 1 查询的字段 QueryBuilder query = QueryBuilders.rangeQuery("price").gte(3000).lte(4000); // 添加查询条件 sourceBuilder.query(query); // 添加过滤 String[] includes = {"id", "title", "price"}; String[] excludes = {}; sourceBuilder.fetchSource(includes, excludes); // 添加查询源 request.source(sourceBuilder); // 发送请求 SearchResponse response = client.search(request); // 分析响应结果 // 返回命中的数据对象 SearchHits searchHits = response.getHits(); // 获取命中的文档个数 long totalHits = searchHits.totalHits; System.out.println("命中的文档个数为: " + totalHits); // 获取命中的数据 SearchHit[] hits = searchHits.getHits(); // 遍历数组 for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); // 转换成对象 Item item = JSON.parseObject(sourceAsString, Item.class); System.out.println(item); } }
示例:
// 排序
sourceBuilder.sort("price", SortOrder.DESC);
示例:
// 分页
int current = 1;
int size = 2;
int start = (current - 1) * size;
sourceBuilder.from(start);
sourceBuilder.size(size);
// 搜索
SearchResponse response = client.search(request);
示例:
@Test public void testHighlight() throws IOException{ // 创建搜索对象 SearchRequest request = new SearchRequest(); // 指定查询的源 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 添加查询条件,通过QueryBuilders获取各种查询 sourceBuilder.query(QueryBuilders.matchQuery("title", "小米手机")); // 高亮 HighlightBuilder highlightBuilder = new HighlightBuilder() //创建高亮构建器对象 .field("title") // 指定高亮字段 .preTags("<em style='color:red'>") // 添加高亮前缀 .postTags("</em>"); // 添加高亮后缀 sourceBuilder.highlighter(highlightBuilder); request.source(sourceBuilder); // 获取结果 SearchResponse response = client.search(request); SearchHits hits = response.getHits(); SearchHit[] hitList = hits.getHits(); for (SearchHit hit : hitList) { // 获取高亮结果 Map<String, HighlightField> fields = hit.getHighlightFields(); // 取出标题 HighlightField titleField = fields.get("title"); // 拼接为字符串 Text[] fragments = titleField.fragments(); String title = fragments[0].string(); // 获取其它字段,并转换成对象 Item item = JSON.parseObject(hit.getSourceAsString(), Item.class); // 覆盖title item.setTitle(title); System.out.println(item); } }
关键代码:
再来试试聚合,以brand字段来聚合,看看有哪些品牌,每个品牌有多少数量。
聚合关键是弄清楚这几点:
与查询类似,聚合条件通过 sourceBuilder.aggregation() 方法来设置,而参数是一个接口:
同样也不需要自己去new,官方提供了一个工厂帮助创建实例:
示例:
/** * 聚合 */ @Test public void testAgg() throws IOException{ // 创建搜索对象 SearchRequest request = new SearchRequest(); // 指定查询的源 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 添加查询条件,通过QueryBuilders获取各种查询 sourceBuilder.query(QueryBuilders.matchAllQuery()); // 添加排序 sourceBuilder.sort("price", SortOrder.ASC); // 配置size为0,因为不需要数据,只要聚合结果 sourceBuilder.size(0); // 添加聚合 sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brand")); request.source(sourceBuilder); // 获取结果 SearchResponse response = client.search(request); // 获取聚合结果 Aggregations aggregations = response.getAggregations(); // 获取某个聚合 Terms terms = aggregations.get("brandAgg"); // 获取桶 for (Terms.Bucket bucket : terms.getBuckets()) { // 获取key,这里是品牌名称 System.out.println("品牌 : " + bucket.getKeyAsString()); // 获取docCount,就是数量 System.out.println("count: " + bucket.getDocCount()); } }
响应:
品牌 : 华为
count: 2
品牌 : 小米
count: 2
品牌 : 锤子
count: 1
还可以在聚合中添加子聚合
示例:
/** * 聚合 */ @Test public void testAgg() throws IOException{ // 创建搜索对象 SearchRequest request = new SearchRequest(); // 指定查询的源 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 添加查询条件,通过QueryBuilders获取各种查询 sourceBuilder.query(QueryBuilders.matchAllQuery()); // 添加排序 sourceBuilder.sort("price", SortOrder.ASC); // 配置size为0,因为不需要数据,只要聚合结果 sourceBuilder.size(0); // 添加聚合 TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("brandAgg").field("brand"); // 添加子聚合 termsAggregationBuilder.subAggregation(AggregationBuilders.avg("avgPrice").field("price")); sourceBuilder.aggregation(termsAggregationBuilder); request.source(sourceBuilder); // 获取结果 SearchResponse response = client.search(request); // 获取聚合结果 Aggregations aggregations = response.getAggregations(); // 获取某个聚合 Terms terms = aggregations.get("brandAgg"); // 获取桶 for (Terms.Bucket bucket : terms.getBuckets()) { // 获取key,这里是品牌名称 System.out.println("品牌 : " + bucket.getKeyAsString()); // 获取docCount,就是数量 System.out.println("count: " + bucket.getDocCount()); // 获取子聚合 Avg avgPrice = bucket.getAggregations().get("avgPrice"); System.out.println("均价:" + avgPrice.getValue()); } }
响应:
品牌 : 华为
count: 2
均价:3649.0
品牌 : 小米
count: 2
均价:3799.0
品牌 : 锤子
count: 1
均价:3699.0
Spring Data Elasticsearch 是Spring提供的elasticsearch组件
Spring Data Elasticsearch(以后简称SDE)是Spring Data项目下的一个子模块。
查看 Spring Data的官网:https://spring.io/projects/spring-data
Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是
非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提
高开发效率。
包含很多不同数据操作的模块:
Spring Data Elasticsearch的页面:https://spring.io/projects/spring-data-elasticsearch
特征:
版本关系:
注意:如果使用Spring Boot 2.3.X版本,Spring Data Elasticsearch 使用的是4.0.X,在4版本后很多API与ElasticSearch 6.X兼容性不是很好,所以要将Spring Boot的版本控制在2.1.X-2.2.X。
官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/3.0.1.RELEASE/reference/html/#reference
集成步骤:
依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version> </dependency> </dependencies>
配置文件中添加 ES 地址
Spring Data Elasticsearch 已经配置好了各种SDE配置,并且注册了一个 ElasticsearchTemplate 以供使用
ElasticsearchTemplate 底层使用的不是 Elasticsearch 提供的 RestHighLevelClient,而是 TransportClient,并不采用 Http 协议通信,而是访问 elasticsearch 对外开放的 tcp 端口,所以这里设置的端口是:9300 ,而不是9200
spring:
data:
elasticsearch:
# ES 集群名称
cluster-name: elasticsearch
# 这里使用的是TransportClient 连接的是TCP端口
cluster-nodes: localhost:9300,localhost:9301,localhost:9302
添加测试类,这里需要注意 SpringBoot 2.1.X 的测试类上需要添加 @RunWith(SpringRunner.class)
,注入ElasticsearchTemplate
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsDemoApplicationTests {
@Autowired
private ElasticsearchTemplate template;
}
准备一个实体类
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "goods",type = "_doc",shards = 3, replicas = 1) public class Goods { @Id private 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(type = FieldType.Keyword, index = false) private String images; // 图片地址 }
用到的注解的说明:
创建索引库
/**
* 创建索引
*/
@Test
void testCreateIndex(){
boolean b = template.createIndex(Goods.class);
System.out.println("结果为:"+b);
}
若不加@Document注解,直接运行会报错,如下所示:
加@Document注解再次运行测试,可以成功创建索引,看一下索引信息
@Id 和 @Filed 注解用于配置映射关系,使用 putMapping() 方法即可创建映射:
/**
* 创建类型映射
*/
@Test
public void testCreateMapping() {
boolean b = template.putMapping(Goods.class);
System.out.println("结果为:" + b);
}
查看索引信息
@Autowired
private ElasticsearchTemplate template;
@Test
public void add(){
Goods goods = new Goods(1L,"小米手机10Pro","手机","小米",5999.00,"/images/123.jpg");
IndexQuery query = new IndexQuery();
query.setObject(goods);
String index = template.index(query);
System.out.println(index);
}
ElasticsearchRepository
封装了基本的CRUD方法,可以通过继承 ElasticsearchRepository
来使用:
public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {
}
ElasticsearchRepository<T, ID>
中的T对应类型,ID对应主键类型。
新增单个文档
// 保存文档
@Test
public void testSave() {
Goods item = new Goods(6L, "小米手机10Pro", " 手机", "小米", 4699.00, "http://image.leyou.com/13123.jpg");
goodsRepository.save(item);
}
更新文档
// 更新文档
@Test
public void testUpdate() {
Goods item = new Goods(6L, "小米手机10Pro", " 手机", "小米", 4699.00, "http://image.leyou.com/13123.jpg");
goodsRepository.save(item);
}
批量新增
@Test
// 批量保存文档
public void addDocuments() {
// 准备文档数据:
List<Goods> list = new ArrayList<>();
list.add(new Goods(1L, "小米手机7", "手机", "小米", 3299.00, "/13123.jpg"));
list.add(new Goods(2L, "坚果手机R1", "手机", "锤子", 3699.00, "/13123.jpg"));
list.add(new Goods(3L, "华为META10", "手机", "华为", 4499.00, "/13123.jpg"));
list.add(new Goods(4L, "小米Mix2S", "手机", "小米", 4299.00, "/13123.jpg"));
list.add(new Goods(5L, "荣耀V10", "手机", "华为", 2799.00, "/13123.jpg"));
// 添加索引数据
goodsRepository.saveAll(list);
}
根据id查询
// 根据id查询
@Test
public void testQueryById(){
Optional<Goods> goodsOptional = goodsRepository.findById(3L);
System.out.println(goodsOptional.orElse(null));
}
根据id删除
// 删除文档
@Test
public void testDelete(){
goodsRepository.deleteById(6L);
}
// 查询所有
@Test
public void testQueryAll(){
Iterable<Goods> list = goodsRepository.findAll();
list.forEach(System.out::println);
}
GoodsRepository提供的查询方法有限,但是它却提供了非常强大的自定义查询功能:只要遵循SpringData提供的语法,就可以任意定义方法声明:
public interface GoodsRepository extends ElasticsearchRepository<Goods, Long> {
/**
* 根据价格区间查询
* @param from 开始价格
* @param to 结束价格
* @return 符合条件的goods
*/
List<Goods> findByPriceBetween(double from, double to);
List<Goods> findByTitle(String title);
List<Goods> findByBrand(String brand);
}
使用实例:
// 范围查询
@Test
public void testConditionSearch(){
List<Goods> list = goodsRepository.findByPriceBetween(3000, 4000);
list.forEach(System.out::println);
}
@Test
public void testTitle(){
// List<Goods> goods = goodsRepository.findByBrand("米");
List<Goods> goods = goodsRepository.findByBrand("小米");
goods.forEach(System.out::println);
}
支持的一些语法示例:
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}}}} |
@Test
public void testSearch(){
// QueryBuilder query = QueryBuilders.matchAllQuery();
QueryBuilder query = QueryBuilders.matchQuery("title","小米");
Iterable<Goods> goods = goodsRepository.search(query);
goods.forEach(System.out::println);
}
@Test
public void testPage(){
QueryBuilder query = QueryBuilders.matchAllQuery();
// 设置分页 page是从0开始
PageRequest pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "price"));
Page<Goods> goodsPage = goodsRepository.search(query, pageable);
System.out.println("总数:" + goodsPage.getTotalElements());
List<Goods> goods = goodsPage.getContent();
goods.forEach(System.out::println);
}
SDE也支持使用 ElasticsearchTemplate 进行原生查询,而查询条件的构建是通过一个名为 NativeSearchQueryBuilder 的类来完成的,不过这个类的底层还是使用的原生API中的 QueryBuilders 、 AggregationBuilders 、 HighlightBuilders 等工具。
要支持高亮,必须自定义结果处理器来实现
import com.itheima.es.entity.Goods; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.Aggregations; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.SearchResultMapper; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl; import java.util.ArrayList; import java.util.List; public class GoodsSearchResultMapper implements SearchResultMapper { @Override public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { SearchHits searchHits = response.getHits(); long total = searchHits.getTotalHits(); float maxScore = searchHits.getMaxScore(); // 定义content List<T> content = new ArrayList<>(); SearchHit[] hits = searchHits.getHits(); // 遍历文档 for (SearchHit hit : hits) { // 获取json格式数据 String sourceAsString = hit.getSourceAsString(); // 转换称为对象 Goods goods = JSON.parseObject(sourceAsString, Goods.class); // 解析高亮字段 String title = hit.getHighlightFields().get("title").fragments()[0].string(); // 替换原有的title goods.setTitle(title); content.add((T) goods); } Aggregations aggregations = response.getAggregations(); String scrollId = response.getScrollId(); return new AggregatedPageImpl(content,pageable,total,aggregations,scrollId,maxScore); } }
查询时需要传入自定义结果处理器
/** * 查询结果高亮处理 */ @Test public void testHighlight() { // 构建查询条件 QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米"); // 定义高亮条件 HighlightBuilder.Field field = new HighlightBuilder.Field("title") .preTags("<em style='color:red'>") .postTags("</em>"); // 构建查询条件并设置高亮 SearchQuery query = new NativeSearchQueryBuilder() .withQuery(queryBuilder) .withHighlightFields(field) .build(); AggregatedPage<Goods> aggregatedPage = template.queryForPage(query, Goods.class, new GoodsSearchResultMapper()); List<Goods> goods = aggregatedPage.getContent(); goods.forEach(System.out::println); }
查看结果:
示例:
/** * 聚合 */ @Test void testAgg() { // 针对品牌字段做分组 AbstractAggregationBuilder agg = AggregationBuilders.terms("brandAgg").field("brand"); // 添加子聚合来实现平均值的计算 agg.subAggregation(AggregationBuilders.avg("priceAvg").field("price")); SearchQuery query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .addAggregation(agg) .build(); AggregatedPage<Goods> aggregatedPage = template.queryForPage(query, Goods.class); // 获取到品牌的聚合 Terms brandAgg = (Terms) aggregatedPage.getAggregation("brandAgg"); for (Terms.Bucket bucket : brandAgg.getBuckets()) { System.out.println("品牌:" + bucket.getKeyAsString()); System.out.println("数量:" + bucket.getDocCount()); Avg priceAvg = bucket.getAggregations().get("priceAvg"); System.out.println("均价:" + priceAvg.getValue()); } }
结果:
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。