客户端初始化
Elasticsearch目前最新版本是8.0,其java客户端有很大变化。不过大多数企业使用的还是8以下版本,所以我们选择使用早期的lavaRestClient客户端来学习。
官方文档地址: Elasticsearch Clients | Elastic
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient
的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
分为三步:
1)在item-service
模块中引入es
的RestHighLevelClient
依赖:
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因为SpringBoot默认的ES版本是7.17.10
,所以我们需要覆盖默认的ES版本:
<properties><elasticsearch.version>7.12.1</elasticsearch.version></properties>
3)初始化RestHighLevelClient:
初始化的语法如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.150.101:9200")
));
为了单元测试方便,我们创建一个测试类IndexTest
,然后将初始化的代码编写在@BeforeEach
方法中:
public class ElasticTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://...:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
启动测试
商品表Mapping映射
我们要实现商品搜索,那么索引库的字段肯定要满足页面搜索的需求:
# 商品索引库
PUT /hmall
{"mappings": {"properties": {"id": {"type": "keyword"},"name": {"type": "text","analyzer": ":ik_smart" # 分词器类型},"price": {"type": "integer"},"image": {"type": "keyword","index": false # 不需要参与搜索},"category": {"type": "keyword"},"brabd": {"type": "keyword"},"sold": {"type": "integer"},"commentCount": {"type": "integer","index": false}"isAD": {"type": "boolean"},"updateTime": {"type": "date"}}}
索引库操作
创建索引库的JavaAPI与Restful接口API对比:
删除索引库
查询索引库
代码示例
public class ElasticIndexTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testCreateIndex() throws IOException {// 1. 准备Request对象CreateIndexRequest request = new CreateIndexRequest("items");// 2. 准备请求参数// request.source(请求体数据, 请求体类型);request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3. 发送请求// client.indices() 得到索引库操作对象// client.indices().create(请求参数, 请求可选项)// RequestOptions.DEFAULT 默认的请求选项client.indices().create(request, RequestOptions.DEFAULT);}@Testvoid testGetIndex() throws IOException {// 1. 准备Request对象GetIndexRequest request = new GetIndexRequest("items");// 2. 发送请求// 查询索引库, 返回索引库信息// GetIndexResponse res = client.indices().get(request, RequestOptions.DEFAULT);// 查询索引库是否存在, 返回布尔值boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);System.out.println("exists = " + exists);}@Testvoid testDelIndex() throws IOException {// 1. 准备Request对象DeleteIndexRequest request = new DeleteIndexRequest("items");// 2. 发送请求client.indices().delete(request, RequestOptions.DEFAULT);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}private static final String MAPPING_TEMPLATE = "{\n" +" \"mappings\": {\n" +" \"properties\": {\n" +" \"id\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"name\": {\n" +" \"type\": \"text\",\n" +" \"analyzer\": \"ik_smart\"\n" +" },\n" +" \"price\": {\n" +" \"type\": \"integer\"\n" +" },\n" +" \"image\": {\n" +" \"type\": \"keyword\",\n" +" \"index\": false\n" +" },\n" +" \"category\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"brabd\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"sold\": {\n" +" \"type\": \"integer\"\n" +" },\n" +" \"commentCount\": {\n" +" \"type\": \"integer\",\n" +" \"index\": false\n" +" },\n" +" \"isAD\": {\n" +" \"type\": \"boolean\"\n" +" },\n" +" \"updateTime\": {\n" +" \"type\": \"date\"\n" +" }\n" +" }\n" +" }\n" +"}";
}
文档操作
1. 新增文档
- 索引库准备好以后,就可以操作文档了。为了与索引库操作分离,我们创建一个测试类,做两件事情:
- 初始化RestHighLevelClient
- 我们的商品数据在数据库,需要利用IHotelService去查询,所以注入这个接口
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {private RestHighLevelClient client;// 这个接口查询数据要访问数据库// 所以要通过properties配置环境变量,激活配置文件 此处是local// 配置文件激活后才能拿到访问数据库的相关数据@Autowiredprivate IItemService itemService;@Testvoid test() {System.out.println("client =" + client);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
- 商品的索引库结构和数据库结构存在查询, 要定义一个与索引库结构对应的实体
@Data
@ApiModel(description = "索引库实体")
public class ItemDoc{@ApiModelProperty("商品id")private String id;@ApiModelProperty("商品名称")private String name;@ApiModelProperty("价格(分)")private Integer price;@ApiModelProperty("商品图片")private String image;@ApiModelProperty("类目名称")private String category;@ApiModelProperty("品牌名称")private String brand;@ApiModelProperty("销量")private Integer sold;@ApiModelProperty("评论数")private Integer commentCount;@ApiModelProperty("是否是推广广告,true/false")private Boolean isAD;@ApiModelProperty("更新时间")private LocalDateTime updateTime;
}
- 新增文档的JavaAPI如下:
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {private RestHighLevelClient client;// 这个接口查询数据要访问数据库// 所以要通过properties配置环境变量,激活配置文件 此处是local// 配置文件激活后才能拿到访问数据库的相关数据@Autowiredprivate IItemService itemService;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testAddDoc() throws IOException {//0 准备文档数据//根据id查询数据库数据Item item = itemService.getById(317578);//数据库的商品数据转为搜索库的文档数据ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);//1 准备RequestIndexRequest request = new IndexRequest("items").id(itemDoc.getId());//2 准备请求参数request.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);//3 发送请求client.index(request, RequestOptions.DEFAULT);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
- 使用jdk17, 需要增加一个测试启动项的vm配置, 不然会因为高版本造成 seata 报错
- 验证文档是否添加成功
2. 查询文档
查询文档包含查询和解析响应结果两部分。对应的IavaAPI如下
public class ElasticDocumentTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testGetDoc() throws IOException {//1 准备RequestGetRequest request = new GetRequest("items", "317578");//2 发送请求GetResponse response = client.get(request, RequestOptions.DEFAULT);//3 解析结果String json = response.getSourceAsString();ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);System.out.println("doc =" + doc);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
3. 删除文档
删除文档的JavaAPI如下:
public class ElasticDocumentTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testDelDoc() throws IOException {//1 准备RequestDeleteRequest request = new DeleteRequest("items", "317578");//2 发送请求client.delete(request, RequestOptions.DEFAULT);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
4. 修改文档
修改文档数据有两种方式:
方式一: 全量更新。再次写入id一样的文档,就会删除旧文档,添加新文档。与新增的JavaAPI一致。
方式二: 局部更新。只更新指定部分字段,
public class ElasticDocumentTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testUpdateDoc() throws IOException {//1 准备RequestUpdateRequest request = new UpdateRequest("items", "317578");//2 准备请求参数request.doc("price", 100);//3 发送请求client.update(request, RequestOptions.DEFAULT);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
5. 批量操作
批处理代码流程与之前类似,只不过构建请求会用到一个名为BulkRequest来封装普通的CRUD请求:
批处理的API示例:
public class ElasticDocumentTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testBulkDoc() throws IOException {//1 准备RequestBulkRequest request = new BulkRequest();//2 准备请求参数request.add(new IndexRequest("items").id("1").source("json", XContentType.JSON));request.add(new DeleteRequest("items").id("1"));//3 发送请求client.bulk(request, RequestOptions.DEFAULT);}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}
把数据库的数据批量添加到索引库
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {private RestHighLevelClient client;// 这个接口查询数据要访问数据库// 所以要通过properties配置环境变量,激活配置文件 此处是local// 配置文件激活后才能拿到访问数据库的相关数据@Autowiredprivate IItemService itemService;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testBulkDoc() throws IOException {int pageNo = 1, pageSize = 500;while (true) {//1 准备文档数据Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page(pageNo, pageSize));List<Item> records = page.getRecords();if (records.isEmpty() || records == null) {return;}//2 准备RequestBulkRequest request = new BulkRequest();//3 准备请求参数for (Item item : records) {request.add(new IndexRequest("items").id(item.getId().toString()).source(JSONUtil.toJsonStr(BeanUtil.copyProperties(item, ItemDoc.class)), XContentType.JSON));}//4 发送请求client.bulk(request, RequestOptions.DEFAULT);//5 翻页pageNo++;}}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}