目录
前言
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布, 是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索、稳定、可靠、快速、安装使用方便。
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。其中的Java Rest Client又包括两种:Java Low Level Rest Client和Java High Level Rest Client。本篇博客是使用Java High Level Rest Client客户端API。
数据库”tb_hotel.sql“已提供。
数据结构如下:
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
`address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
`price` int(10) NOT NULL COMMENT '酒店价格;例:329',
`score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
`brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
`city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
`star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1
钻到5钻',
`business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
`latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
`longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
mapping映射分析:酒店数据的索引库结构
PUT /hotels
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false,
"copy_to": "all"
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
注意:
(1)字段:
location:地理坐标,里面包含精度、纬度;
all:一个组合字段,其目的是将多字段的值利用copy_to合并,提供给用户搜索。
(2)属性:
copy_to:字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段。
(3)类型:
geo_point:由进度和纬度确定一个点,如”32.875, 129.298“,注意精度和纬度值之间逗号之后的空格,固定语法。
一、初始化RestClient
在ElasticSearch提供的API中,与ElasticSearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与ElasticSearch的连接。
分为三步:
1)引入es的RestHighLevelClient依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:(非必要)
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.0</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")
));
这里为了单元测试方便,我们创建一个测试类HotelIndexTest,然后将初始化的代码以及文档操作的代码编写在该类中,同时将初始化的代码编写在 @BeforeEach方法中:
public class HotelIndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
二、索引库操作
创建索引库的API如下:
创建一个HotelConstants类,定义mapping映射的JSON字符串常量:
public class HotelConstants {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"address\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"price\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"score\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"brand\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"city\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"starName\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"location\":{\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"pic\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"all\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
1、判断索引库是否存在
在HotelIndexTest测试类中,编写单元测试,实现判断索引是否存在:
@Test
void testExistsHotelIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}
2、创建索引库
在HotelIndexTest测试类中,编写单元测试,实现创建索引:
@Test
void createHotelIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求的参数:DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
3、删除索引库
在HotelIndexTest测试类中,编写单元测试,实现删除索引:
@Test
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
HotelIndexTest测试类完整代码:
@SpringBootTest
public class HotelIndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp(){
this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
@Test
void testExistsHotelIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("hotels");
boolean isExist = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(isExist ? "索引库已经存在!":"索引库不存在!");
}
//创建索引库
@Test
void createHotelIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("hotels");
//准备请求的参数:DSL语句
request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
//发送请求
client.indices().create(request,RequestOptions.DEFAULT);
}
//删除索引库
@Test
void testDeleteHotelIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("hotels");
client.indices().delete(request,RequestOptions.DEFAULT);
}
}
三、文档操作
与数据库相关的实体类:Hotel类:
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(value = "id",type = IdType.INPUT)
private Long id;
@TableField(value = "name")
private String name;
@TableField(value = "address")
private String address;
@TableField(value = "price")
private Integer price;
@TableField(value = "score")
private Integer score;
@TableField(value = "brand")
private String brand;
@TableField(value = "city")
private String city;
@TableField(value = "star_name")
private String starName;
@TableField(value = "business")
private String business;
@TableField(value = "longitude")
private String longitude;//经度
@TableField(value = "latitude")
private String latitude;//纬度
@TableField(value = "pic")
private String pic;
}
我们需要定义一个新的类型,与索引库结构吻合:为ES索引库设计实体类HotelDoc类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
1、新增文档
在HotelDocumentTest测试类(自建)中,编写单元测试:
//文档新增
@Test
void testAddDocument() throws IOException {
//根据id查询酒店数据
Hotel hotel = service.getById(197837109);
//System.out.println(hotel);
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
//将HotelDoc转json
String json = JSON.toJSONString(hotelDoc);
//准备Request对象
IndexRequest request = new IndexRequest("hotels").id(hotelDoc.getId().toString());
//准备Json文档
request.source(json, XContentType.JSON);
//发送请求
client.index(request, RequestOptions.DEFAULT);
}
2、查询文档
在HotelDocumentTest测试类(自建)中,编写单元测试:
//查询文档
@Test
void testGetDocumentById() throws IOException {
//准备Request
GetRequest request = new GetRequest("hotels", "197837109");
//发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
3、删除文档
在HotelDocumentTest测试类(自建)中,编写单元测试:
//文档删除
@Test
void testDeleteDocument() throws IOException {
DeleteRequest request = new DeleteRequest("hotels", "197837109");
client.delete(request, RequestOptions.DEFAULT);
}
4、修改文档
在HotelDocumentTest测试类(自建)中,编写单元测试:
//修改文档
@Test
void testUpdateDocument() throws IOException {
UpdateRequest request = new UpdateRequest("hotels", "197837109");
request.doc("name", "W酒店",
"city", "西安",
"price", "2000",
"starName", "五星级");
client.update(request, RequestOptions.DEFAULT);
}
5、批量导入文档
在HotelDocumentTest测试类(自建)中,编写单元测试:
//批量新增
@Test
void testBulkRequest() throws IOException {
// 批量查询酒店数据
List<Hotel> hotelList = service.list();
//创建Request
BulkRequest request = new BulkRequest();
//准备参数,添加多个新增的Request
for (Hotel hotel : hotelList) {
//转换为文档类型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
//创建新增文档的Request对象
request.add(new IndexRequest("hotels").id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
//发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
四、ElasticSearch查询
在敲ElasticSearch查询代码之前,我们首先在HotelDocumentTest测试类常见一个show()方法,用来输出查询结果的数量以及数据内容在控制台。
在HotelDocumentTest测试类(自建)中,编写show()方法:
//解析响应对象
public void show(SearchResponse response) {
//解析响应
SearchHits searchHits = response.getHits();
//获取总数据数量
long tatal = searchHits.getTotalHits().value;
System.out.println("共搜索到" + tatal + "条数据!");
//文档数组
SearchHit[] hits = searchHits.getHits();
//遍历
for (SearchHit searchHit : hits) {
String json = searchHit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
}
}
1、查询所有
在HotelDocumentTest测试类(自建)中,编写单元测试:
//查询所有
@Test
void testMatchAll() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotels");
//准备DSL,QueryBuilders构造查询条件
request.source().query(QueryBuilders.matchAllQuery());
//发送求情
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
show(response);
}
2、全文检索查询(模糊查询、or拼接)
在HotelDocumentTest测试类(自建)中,编写单元测试:
(1)match_query:单字段查询
//查询all字段内容中有如家的(or拼接多条件)
@Test
void testMatch() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotels");
//准备DSL 参数1:字段 参数2:数据
request.source().query(QueryBuilders.matchQuery("all","如家"));
//发送请求
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
(2)multi_match_query:多字段查询
//查询name,business字段内容中有如家的
@Test
void testMultiMatch() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotels");
//准备DSL 参数1:字段 参数2:数据
request.source().query(QueryBuilders.multiMatchQuery("如家","name","business"));
//发送请求
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
3、精确查询
在HotelDocumentTest测试类(自建)中,编写单元测试:
(1)term根据词条精确值查询
//精确查询
//查询在上海的酒店数据
@Test
void testTermQuery() throws IOException {
SearchRequest request = new SearchRequest("hotels");
//准备DSL,QueryBuilders构造查询条件
request.source().query(QueryBuilders.termQuery("city","上海"));
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
(2) range根据值的范围查询
//范围查询
//查询价格大于等于100小于等于150的酒店信息
@Test
void testRangeQuery() throws IOException {
SearchRequest request = new SearchRequest("hotels");
request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
4、复合查询
在HotelDocumentTest测试类(自建)中,编写单元测试:
//复合查询(bool)
//查询城市在上海且价格小于等于260的酒店信息
@Test
void testBool() throws IOException {
SearchRequest request = new SearchRequest("hotels");
request.source().query(
QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("city","上海"))
.filter(QueryBuilders.rangeQuery("price").lte(260)));
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
布尔查询是一个或多个查询子句的组合,子查询的组合方式有:
must:必须匹配每个子查询,类似“与”;
should:选择性匹配子查询,类似“或”;
must_not:必须不匹配,不参与算分,类似“非”;
filter:必须匹配,类似“与”,不参与算分,一般搜索框用must,选择条件使用filter。
5、控制页以及排序
在HotelDocumentTest测试类(自建)中,编写单元测试:
控制在控制台打印输出时显示第几页的数据以及显示数据的多少,以及按照哪个字段降序排列或升序排列。
//查询时对分页以及排序做出控制
//显示第二页数据,显示5行,按照价格降序排列
@Test
void testPageAndSort() throws IOException {
//页码,每页大小
int page = 2,size=5;
SearchRequest request = new SearchRequest("hotels");
request.source().query(QueryBuilders.matchAllQuery());
//分页 from、size
request.source().from((page-1)*size).size(size);
//排序
request.source().sort("price", SortOrder.DESC);
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
HotelDocumentTest测试类完整代码:
@SpringBootTest
class HotelDocumentTest {
private RestHighLevelClient client;
@Autowired
private HotelServiceImp service;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
/*************************文档增,删,修,查*********************************/
//文档新增
@Test
void testAddDocument() throws IOException {
//根据id查询酒店数据
Hotel hotel = service.getById(197837109);
//System.out.println(hotel);
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
//将HotelDoc转json
String json = JSON.toJSONString(hotelDoc);
//准备Request对象
IndexRequest request = new IndexRequest("hotels").id(hotelDoc.getId().toString());
//准备Json文档
request.source(json, XContentType.JSON);
//发送请求
client.index(request, RequestOptions.DEFAULT);
}
//修改文档
@Test
void testUpdateDocument() throws IOException {
UpdateRequest request = new UpdateRequest("hotels", "197837109");
request.doc("name", "W酒店",
"city", "西安",
"price", "2000",
"starName", "五星级");
client.update(request, RequestOptions.DEFAULT);
}
//查询文档
@Test
void testGetDocumentById() throws IOException {
//准备Request
GetRequest request = new GetRequest("hotels", "197837109");
//发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//解析响应结果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
//文档删除
@Test
void testDeleteDocument() throws IOException {
DeleteRequest request = new DeleteRequest("hotels", "197837109");
client.delete(request, RequestOptions.DEFAULT);
}
//批量新增
@Test
void testBulkRequest() throws IOException {
// 批量查询酒店数据
List<Hotel> hotelList = service.list();
//创建Request
BulkRequest request = new BulkRequest();
//准备参数,添加多个新增的Request
for (Hotel hotel : hotelList) {
//转换为文档类型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
//创建新增文档的Request对象
request.add(new IndexRequest("hotels").id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
//发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
/*****************************全文检索*********************************/
//查询所有
@Test
void testMatchAll() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotels");
//准备DSL,QueryBuilders构造查询条件
request.source().query(QueryBuilders.matchAllQuery());
//发送求情
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
show(response);
}
//查询all字段内容中有如家的(or拼接多条件)
@Test
void testMatch() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotels");
//准备DSL 参数1:字段 参数2:数据
request.source().query(QueryBuilders.matchQuery("all","如家"));
//发送请求
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
//查询name,business字段内容中有如家的
@Test
void testMultiMatch() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotels");
//准备DSL 参数1:字段 参数2:数据
request.source().query(QueryBuilders.multiMatchQuery("如家","name","business"));
//发送请求
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
/***************************精确检索*********************************/
//精确查询
//查询在上海的酒店数据
@Test
void testTermQuery() throws IOException {
SearchRequest request = new SearchRequest("hotels");
//准备DSL,QueryBuilders构造查询条件
request.source().query(QueryBuilders.termQuery("city","上海"));
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
//范围查询
//查询价格大于等于100小于等于150的酒店信息
@Test
void testRangeQuery() throws IOException {
SearchRequest request = new SearchRequest("hotels");
request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
//复合查询(bool)
//查询城市在上海且价格小于等于260的酒店信息
@Test
void testBool() throws IOException {
SearchRequest request = new SearchRequest("hotels");
request.source().query(
QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("city","上海"))
.filter(QueryBuilders.rangeQuery("price").lte(260)));
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
//查询时对分页以及排序做出控制
//显示第二页数据,显示5行,按照价格降序排列
@Test
void testPageAndSort() throws IOException {
//页码,每页大小
int page = 2,size=5;
SearchRequest request = new SearchRequest("hotels");
request.source().query(QueryBuilders.matchAllQuery());
//分页 from、size
request.source().from((page-1)*size).size(size);
//排序
request.source().sort("price", SortOrder.DESC);
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
show(response);
}
//解析响应对象
public void show(SearchResponse response) {
//解析响应
SearchHits searchHits = response.getHits();
//获取总数据数量
long tatal = searchHits.getTotalHits().value;
System.out.println("共搜索到" + tatal + "条数据!");
//文档数组
SearchHit[] hits = searchHits.getHits();
//遍历
for (SearchHit searchHit : hits) {
String json = searchHit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
}
}
总结
JavaRestClient操作ElasticSearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对 象。 索引库操作的基本步骤:
(1)初始化RestHighLevelClient;
(2)创建XxxIndexRequest。XXX是Create、Get、Delete;
(3)准备DSL( Create时需要,其它是无参);
(4)发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete。
文档操作的基本步骤:
(1)初始化RestHighLevelClient;
(2)创建XxxRequest。Xxx是Index、Get、Update、Delete、Bulk;
(3)准备参数(Index、Update、Bulk时需要) 发送请求。
(4)调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk;
(5)解析结果(Get时需要)。