本文最后更新于:2 个月前
ES & SpringData ES 创建项目 elasticsearch-demo
导入pom文件,ES依赖的版本最好和客户端对应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.1.RELEASE</version > <relativePath /> </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.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-high-level-client</artifactId > <version > 7.7.0</version > </dependency > <dependency > <groupId > org.elasticsearch.client</groupId > <artifactId > elasticsearch-rest-client</artifactId > <version > 7.7.0</version > </dependency > <dependency > <groupId > org.elasticsearch</groupId > <artifactId > elasticsearch</artifactId > <version > 7.7.0</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.4</version > </dependency > </dependencies >
1.在 resource
文件夹下面创建 application.yml
文件
1 2 3 elasticsearch: host: IP地址 port: 9200
2.启动类
1 2 3 4 5 6 7 8 @SpringBootApplication public class ElasticsearchDemoApplication { public static void main (String[] args) { SpringApplication.run(ElasticsearchDemoApplication.class, args); } }
3.创建 com.atguigu.config.ElasticSearchConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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; } @Bean public RestHighLevelClient client () { return new RestHighLevelClient(RestClient.builder( new HttpHost( host, port, "http" ) )); } }
新建测试类
1 2 3 4 5 6 7 8 9 10 package com.atguigu.test;@RunWith(SpringRunner.class) @SpringBootTest public class ElasticsearchTest { @Autowired private RestHighLevelClient client; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void addIndex () throws Exception { IndicesClient indicesClient = client.indices(); CreateIndexRequest createRequest = new CreateIndexRequest("abc " ); CreateIndexResponse response = indicesClient.create(createRequest, RequestOptions.DEFAULT); System.out.println(response.isAcknowledged()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Test public void addIndexAndMapping () throws IOException { IndicesClient indicesClient = client.indices(); CreateIndexRequest createRequest = new CreateIndexRequest("aaa" ); String 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); System.out.println(response.isAcknowledged()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public 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()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public 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()); }
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void existIndex () throws IOException { IndicesClient indices = client.indices(); GetIndexRequest getRequest = new GetIndexRequest("aaa" ); boolean exists = indices.exists(getRequest, RequestOptions.DEFAULT); System.out.println(exists); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void addDoc () throws IOException { Map data = new HashMap(); data.put("address" ,"深圳宝安" ); data.put("name" ,"尚硅谷" ); data.put("age" ,20 ); IndexRequest request = new IndexRequest("aaa" ).id("1" ).source(data); IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.getId()); }
创建 com.atguigu.domain.Person
1 2 3 4 5 6 7 public class Person { private String id; private String name; private int age; private String address; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void addDoc2 () throws IOException { Person p = new Person(); p.setId("2" ); p.setName("硅谷2222" ); p.setAge(30 ); p.setAddress("北京昌平区" ); String data = JSON.toJSONString(p); IndexRequest request = new IndexRequest("aaa" ).id(p.getId()).source(data,XContentType.JSON); IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.getId()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void updateDoc () throws IOException { Person p = new Person(); p.setId("2" ); p.setName("硅谷" ); p.setAge(30 ); p.setAddress("北京昌平区" ); String data = JSON.toJSONString(p); IndexRequest request = new IndexRequest("aaa" ).id(p.getId()).source(data,XContentType.JSON); IndexResponse response = client.index(request, RequestOptions.DEFAULT); System.out.println(response.getId()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void findDocById () throws IOException { GetRequest getReqeust = new GetRequest("aaa" ,"1" ); GetResponse response = client.get(getReqeust, RequestOptions.DEFAULT); System.out.println(response.getSourceAsString()); }
1 2 3 4 5 6 7 8 @Test public void delDoc () throws IOException { DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT); System.out.println(response.getId()); }
批量操作-脚本 Bulk 批量操作是将文档的增删改查一些列操作,通过一次请求全都做完。减少网络传输次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Test public void testBulk () throws IOException { BulkRequest bulkRequest = new BulkRequest(); DeleteRequest deleteRequest = new DeleteRequest("person" ,"1" ); bulkRequest.add(deleteRequest); 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" ,"三号" ); 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); }
将数据库中Goods表的数据导入到ElasticSearch中
① 将数据库中Goods表的数据导入到ElasticSearch中
② 创建索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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
title:商品标题
price:商品价格
createTime:创建时间
categoryName:分类名称。如:家电,手机
brandName:品牌名称。如:华为,小米
spec: 商品规格。如: spec:{“屏幕尺寸”,“5寸”,“内存大小”,“128G”}
saleNum:销量
stock:库存量
1 2 3 4 5 6 7 8 9 10 11 create table `goods` ( `id` double , `title` varchar (300), `price` Decimal (22), `stock` double , `saleNum` double , `createTime` datetime , `categoryName` varchar (600), `brandName` varchar (300), `spec` varchar (600) );
测试数据:测试数据:链接:https://pan.baidu.com/s/14V3csJT1Xf2c-cKFDl7lNg 提取码:sxzx
添加文档数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 POST goods/_doc/1 { "title" :"小米手机" , "price" :1000 , "createTime" :"2019-12-01" , "categoryName" :"手机" , "brandName" :"小米" , "saleNum" :3000 , "stock" :10000 , "spec" :{ "网络制式" :"移动4G" , "屏幕尺寸" :"4.5" } } # 查询文档数据 GET goods/_search
添加坐标
1 2 3 4 5 6 7 8 9 10 11 <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
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: datasource: url: jdbc:mysql:///es?serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: com.atguigu.domain
添加 javabean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; private String specStr; }
创建 dao
1 2 3 4 5 6 7 8 9 @Repository @Mapper public interface GoodsMapper { public List<Goods> findAll () ; }
在 resource
文件夹下面 创建 mapper/GoodMapper.xml
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?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.atguigu.mapper.GoodsMapper" > <select id ="findAll" resultType ="goods" > select `id` , `title` , `price` , `stock` , `saleNum` , `createTime` , `categoryName`, `brandName` , `spec` as specStr from goods </select > </mapper >
添加测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @SpringBootTest public class ElasticsearchTest2 { @Autowired private GoodsMapper goodsMapper; @Test public void importData () throws IOException { List<Goods> goodsList = goodsMapper.findAll(); BulkRequest bulkRequest = new BulkRequest(); for (Goods goods : goodsList) { String specStr = goods.getSpecStr(); Map map = JSON.parseObject(specStr, Map.class); goods.setSpec(map); String data = JSON.toJSONString(goods); 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查询:查询所有文档
kibana
演示
1 2 3 4 5 6 7 8 9 10 # 查询 GET goods/_search { "query" : { "match_all" : {} }, "from" : 0 , "size" : 100 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @Test public void testMatchAll () throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); QueryBuilder query = QueryBuilders.matchAllQuery(); sourceBuilder.query(query); searchRequest.source(sourceBuilder); sourceBuilder.from(0 ); sourceBuilder.size(100 ); 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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
term查询:不会对查询条件进行分词。
kibana
演示
1 2 3 4 5 6 7 8 9 10 11 12 13 GET goods # term 查询 GET goods/_search { "query" : { "term" : { "categoryName" : { "value" : "手机" } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @SpringBootTest public class ElasticsearchTest2 { @Test public void testTermQuery () throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); QueryBuilder query = QueryBuilders.termQuery("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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } } }
matchQuery:词条分词查询 match查询: • 会对查询条件进行分词。 • 然后将分词后的查询条件和词条进行等值匹配 • 默认取并集(OR)
kibana
演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # match 查询 "title" : "手机" GET goods/_search { "query" : { "match" : { "title" : "华为" } } } # match 查询 "operator" : "or" GET goods/_search { "query" : { "match" : { "title" : { "query" : "华为手机" , "operator" : "and" } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Test public void testMatchQuery () throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); MatchQueryBuilder query = QueryBuilders.matchQuery("title" , "华为手机" ); query.operator(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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
模糊查询-脚本 wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符) prefix查询:前缀查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # wildcard 查询。查询条件分词,模糊查询 华为,华,*华* GET goods/_search { "query" : { "wildcard" : { "title" : { "value" : "华*" } } } } # 前缀查询 GET goods/_search { "query" : { "prefix" : { "brandName" : { "value" : "三" } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
范围查询-脚本 range 范围查询:查找指定字段在指定范围内包含值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 范围查询 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Test public void testRangeQuery() throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); RangeQueryBuilder query = QueryBuilders.rangeQuery("price" ); 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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); goodsList.add(goods); } for (Goods goods : goodsList) { System.out.println(goods); } }
queryString查询-脚本 queryString: • 会对查询条件进行分词。 • 然后将分词后的查询条件和词条进行等值匹配 • 默认取并集(OR) • 可以指定多个查询字段
1 2 3 4 5 6 7 8 9 10 # queryString GET goods/_search { "query" : { "query_string" : { "fields" : ["title" ,"categoryName" ,"brandName" ], "query" : "华为" } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Test public void testQueryStringQuery() throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); QueryStringQueryBuilder 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(); Goods 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高。不会计算得分
1 2 3 4 5 6 7 8 9 GET goods/_search { "query" : { "match" : { "title" : "华为手机" } }, "size" : 500 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 # 计算得分 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" : "电视" } } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @Test public void testBoolQuery () throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); BoolQueryBuilder query = QueryBuilders.boolQuery(); QueryBuilder termQuery = QueryBuilders.termQuery("brandName" ,"华为" ); query.must(termQuery); QueryBuilder matchQuery = QueryBuilders.matchQuery("title" ,"手机" ); query.filter(matchQuery); QueryBuilder rangeQuery = QueryBuilders.rangeQuery("price" ); ((RangeQueryBuilder) rangeQuery).gte(2000 ); ((RangeQueryBuilder) rangeQuery).lte(3000 ); query.filter(rangeQuery); 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(); Goods 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类型的数据进行分组,会失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # 查询最贵的华为手机,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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @Test public void testAggQuery () throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); MatchQueryBuilder query = QueryBuilders.matchQuery("title" , "手机" ); sourceBulider.query(query); 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(); Goods 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(); 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); } }
高亮三要素: • 高亮字段 • 前缀 • 后缀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 GET goods/_search { "query" : { "match" : { "title" : "电视" } }, "highlight" : { "fields" : { "title" : { "pre_tags" : "<font color='red'>" , "post_tags" : "</font>" } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Test public void testHighLightQuery () throws IOException { SearchRequest searchRequest = new SearchRequest("goods" ); SearchSourceBuilder sourceBulider = new SearchSourceBuilder(); 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); 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(); Goods goods = JSON.parseObject(sourceAsString, Goods.class); Map<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); } }
随着业务需求的变更,索引的结构可能发生改变。 ElasticSearch的索引一旦创建,只允许添加字段,不允许改变字段。因为改变字段,需要重建倒排索引,影响内部缓 存结构,性能太低。 那么此时,就需要重建一个新的索引,并将原有索引的数据导入到新索引中。
原索引库 :student_index_v1
新索引库 :student_index_v2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 # 新建student_index_v1索引,索引名称必须全部小写 PUT student_index_v1 { "mappings" : { "properties" : { "birthday" :{ "type" : "date" } } } } # 查询索引 GET student_index_v1 # 添加数据 PUT student_index_v1/_doc/1 { "birthday" :"2020-11-11" } # 查询数据 GET student_index_v1/_search # 随着业务的变更,换种数据类型进行添加数据,程序会直接报错 PUT student_index_v1/_doc/1 { "birthday" :"2020年11月11号" } # 业务变更,需要改变birthday数据类型为text # 1 :创建新的索引 student_index_v2 # 2 :将student_index_v1 数据拷贝到 student_index_v2 # 创建新的索引 PUT student_index_v2 { "mappings" : { "properties" : { "birthday" :{ "type" : "text" } } } } DELETE student_index_v2 # 2 :将student_index_v1 数据拷贝到 student_index_v2 POST _reindex { "source" : { "index" : "student_index_v1" }, "dest" : { "index" : "student_index_v2" } } # 查询新索引库数据 GET student_index_v2/_search # 在新的索引库里面添加数据 PUT student_index_v2/_doc/2 { "birthday" :"2020年11月13号" }
目标
SpringData的作用:简化了数据库的增删改查操作
SpringDataElasticsearch入门[掌握]
SpringDataElasticsearch查询命名规则[掌握]
SpringDataJpa介绍[集成] JPA是一个规范,真正操作数据库的是Hibernate(实现数据库增删改查框架[ORM框架],操作数据库采用的方式是面向对象[不写SQL语句]),而springdatajpa是对jpa的封装,将CRUD的方法封装到指定的方法中,操作的时候,只需要调用方法即可。
Spring Data Jpa的实现过程:
定义实体,实体类添加Jpa的注解 @Entity @Talbe @Cloumn @Id
定义接口,接口要继承JpaRepository的接口
配置spring容器,applicationContext.xml/SpringApplication.run(T.class,args)
Spring Data ElasticSearch简介 (1)SpringData介绍
Spring Data是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。 Spring Data可以极大的简化JPA(Elasticsearch…)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。
Spring Data的官网:http://projects.spring.io/spring-data/
Spring Data常用的功能模块如下:
(2)SpringData Elasticsearch介绍
Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API 进行封装 。Spring Data为Elasticsearch项目提供集成搜索引擎。Spring Data Elasticsearch POJO的关键功能区域为中心的模型与Elastichsearch交互文档和轻松地编写一个存储索引库数据访问层。
官方网站:http://projects.spring.io/spring-data-elasticsearch/
SpringData Elasticsearch入门 搭建工程 (1)搭建工程
创建项目 elasticsearch-springdata-es
(2)pom.xml依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?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.atguigu</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.2.1.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 > <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 >
增加索引数据 (1)编写实体类
创建com.atguigu.domain.Item
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data @AllArgsConstructor @NoArgsConstructor @ToString @Document(indexName = "item",shards = 1,replicas = 1) public class Item { @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(index = false, type = FieldType.Keyword) private String images; }
映射
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
@Document 作用在类,标记实体类为文档对象,一般有四个属性 indexName:对应索引库名称 shards:分片数量,默认5 replicas:副本数量,默认1 @Id 作用在成员变量,标记一个字段作为id主键 @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性: type:字段类型,取值是枚举:FieldType index:是否索引,布尔类型,默认是true store:是否存储,布尔类型,默认是false analyzer:分词器名称:ik_max_word
配置 application.yml
文件
1 2 3 4 5 spring: data: elasticsearch: cluster-name: elasticsearch #自己ES中配置文件的cluster-name cluster-nodes: 127.0.0.1:9300
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RunWith(SpringRunner.class) @SpringBootTest public class TestSpringBootES { @Autowired private ElasticsearchTemplate elasticsearchTemplate; @Autowired private ItemRepository itemRepository; @Test public void testCreate () { elasticsearchTemplate.createIndex(Item.class); elasticsearchTemplate.putMapping(Item.class); }
使用 kibana
查询
增删改操作
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
编写 ItemRepository
1 2 3 public interface ItemRepository extends ElasticsearchRepository <Item ,Long > { }
增加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RunWith(SpringRunner.class) @SpringBootTest public class TestSpringBootES { @Autowired private ElasticsearchTemplate elasticsearchTemplate; @Autowired private ItemRepository itemRepository; @Test public void testAdd () { Item item = new Item(1L , "小米手机7" , " 手机" , "小米" , 3499.00 , "http://image.leyou.com/13123.jpg" ); itemRepository.save(item); } }
修改(id存在就是修改,否则就是插入)
1 2 3 4 5 6 7 @Test public void testUpdate () { Item item = new Item(1L , "小米手机7777" , " 手机" , "小米" , 9499.00 , "http://image.leyou.com/13123.jpg" ); itemRepository.save(item); } GET item/_search
批量新增
1 2 3 4 5 6 7 8 9 @Test public 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); } GET item/_search
删除操作
1 2 3 4 @Test public void testDelete () { itemRepository.deleteById(1L ); }
根据id查询
1 2 3 4 5 @Test public void testQuery () { Optional<Item> optional = itemRepository.findById(2L ); System.out.println(optional.get()); }
查询全部,并按照价格降序排序
1 2 3 4 5 6 @Test public 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}}}}
例如,我们来按照价格区间查询,定义这样的一个方法:
1 2 3 4 5 6 7 8 9 public interface ItemRepository extends ElasticsearchRepository <Item ,Long > { List<Item> findByPriceBetween (double price1, double price2) ; }
然后添加一些测试数据:
1 2 3 4 5 6 7 8 9 10 11 @Test public void indexList () { 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); }
不需要写实现类,然后我们直接去运行:
1 2 3 4 5 6 7 @Test public void queryByPriceBetween () { List<Item> list = this .itemRepository.findByPriceBetween(2000.00 , 3500.00 ); for (Item item : list) { System.out.println("item = " + item); } }
虽然基本查询和自定义方法已经很强大了,但是如果是复杂查询(模糊、通配符、词条查询等)就显得力不从心了。此时,我们只能使用原生查询。