java操作elasticsearch实现常用的功能
准备工作
首先要求电脑安装elasticsearch以及kibana,本人使用的elasticsearch版本是7.2.1,建议初学者和我使用同一个版本,不然可能会出现莫名其貌的错误。
下载地址:
kibana:链接:https://pan.baidu.com/s/1rvzbFsLej0Ri_vkwmUenIw
提取码:89su
elasticsearch:链接:https://pan.baidu.com/s/1-vUv–bXyBJHnYSFVtztNQ
提取码:dsdf
ik分词器: 下载 https://github.com/medcl/elasticsearch-analysis-ik/releases
安装 解压安装到elasticsearch的plugins目录
启动elasticsearch以及kibana
下载后将两款软件解压,然后首先运行elasticsearch的bin目录的elasticsearch.bat,运行成功后在浏览器输入http://localhost:9200,其中9200是elasticsearch的默认端口号,浏览器返回如下信息即表示启动成功
当elasticsearch启动成功后,打开kibana的bin目录下的kibana.bat,启动kibana,kibana启动会比较慢,大概需要1分钟左右,也不知道是不是因为电脑配置问题。
启动之后,在浏览器访问http://localhost:5601,5601是kibana的默认端口,访问后浏览器出现如下页面,则说明启动成功。
如果启动kibana后页面显示是英文,则打开kibana的conf目录下的kibana.yml,在最后添加
i18n.locale: “zh-CN”
之后,保存,重新启动kibana即可汉化为中文。
elasticsearch与kibana的简介
Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
其实以上都是废话,简单来说,elasticsearch就是一个能够帮我们存储数据并且能够很好的完成搜索的搜索引擎。
kibana是Elasticsearch的可视化⼯工具,使我们不用再使用postman等软件发送rest请求来操作elasticsearch。
elasticsearch与关系型数据库比较:
这个图很关键,因为本文就是教大家如何使用java代码来操作图中的概念。
kibana的简单使用
首先点击后进入控制台,在这里可以进行elasticsearch命令的发送。
我们可以在控制台输入elasticsearch,操作elasticsearch。
然后最下面小齿轮是我们管理界面,在该界面可以查看我们的索引以及映射等信息。
开始构建
首先在springboot的配置文件中加入elasticsearch的配置
elasticsearch:
host: localhost
port: 9200
之后编写一个spring bean来注入spring
@Configuration
public class EsConfig {
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private Integer port;
@Bean(destroyMethod = "close")
public RestHighLevelClient client() {
return new RestHighLevelClient(RestClient.builder(
new HttpHost(host, port, "http")
));
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}
到这里,相当于springboot与elasticsearch已经整合完成了。
下面上核心操作api,以下的代码每操作一部,大家都可以去kibana上面通过RESTful请求查看是否成功。
创建索引index
创建索引index,就相当于我们使用java创建数据库。
@Resource
private RestHighLevelClient client;//连接elasticsearch的客户端,在配置文件中已经注入
private static final String INDEX = "item"; //索引名称
public Object createIndex() {
try {
//构建创造索引请求 构造器参数为你想要创建的索引名称
CreateIndexRequest createIndexRequest = new CreateIndexRequest(INDEX);
//发送请求
CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
//isAcknowledged()是否创建完成
if (createIndexResponse.isAcknowledged()) {
return true;
}
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
创建映射Mapping
创建索引mapping相当于我们创建数据库表,各个字段分别匹配实体类的属性。
首先我们需要一个Java实体类来对应es的mapping
public class Item {
private Long id;
private String type;
private String title;
private String sellPoint;
private BigDecimal price;
private Integer num;
private Integer status;
private Date createTime;
private Date updateTime;
public Item() {
}
public Item(Long id, String type, String title, String sellPoint, BigDecimal price, Integer num, Integer status, Date createTime, Date updateTime) {
this.id = id;
this.type = type;
this.title = title;
this.sellPoint = sellPoint;
this.price = price;
this.num = num;
this.status = status;
this.createTime = createTime;
this.updateTime = updateTime;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSellPoint() {
return sellPoint;
}
public void setSellPoint(String sellPoint) {
this.sellPoint = sellPoint;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
/**
* 需要分词的字段
* @return
*/
public static List<String> list(){
List<String> list = new ArrayList<>();
list.add("title");
return list;
}
}
操作类:
@Override
public Object createMapping() {
//构建PutMapping请求
PutMappingRequest putMappingRequest = new PutMappingRequest(INDEX);
try {
//XContentBuilder构造器,构造一个el使用的类json字符串来创建mapping
XContentBuilder xContentBuilder = setMapping(new Item());
//将XContentBuilder构造器放入请求中
putMappingRequest.source(xContentBuilder);
//构建的类json字符串,不懂得可以将下面这行代码打印,即可知道数据类型
xContentBuilder.getOutputStream().toString();
//获取IndicesClient客户端
IndicesClient indicesClient = client.indices();
//发送请求
AcknowledgedResponse acknowledgedResponse = indicesClient.putMapping(putMappingRequest, RequestOptions.DEFAULT);
//创建成功isAcknowledged()为true
if (acknowledgedResponse.isAcknowledged()) {
return true;
}
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 构建XContentBuilder
* @param obj
* @return
*/
private static XContentBuilder setMapping(Object obj) {
List<Field> fieldList = getFields(obj);
XContentBuilder mapping = null;
try {
mapping = jsonBuilder().startObject().startObject("properties");
for (Field field : fieldList) {
//修饰符是static的字段不处理
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
String name = field.getName();
if (Item.list().contains(name)) {
//需要分词的字段
mapping.startObject(name)
.field("type", getElasticSearchMappingType(field.getType().getSimpleName().toLowerCase()))
.field("index", "true")
//使用IK分词器
.field("analyzer", "ik_max_word")
.field("search_analyzer", "ik_max_word")
.startObject("fields")
.startObject("suggest")
.field("type", "completion")
.field("analyzer", "ik_max_word")
.endObject()
.endObject()
.endObject();
} else {
//不需要分词的字段
mapping.startObject(name)
.field("type", getElasticSearchMappingType(field.getType().getSimpleName().toLowerCase()))
.field("index", "true")
.endObject();
}
}
mapping.endObject().endObject();
} catch (IOException e) {
e.printStackTrace();
}
return mapping;
}
/**
* 获取字段名
*
* @param obj
* @return
*/
private static List<Field> getFields(Object obj) {
Field[] fields = obj.getClass().getDeclaredFields();
List<Field> fieldList = new ArrayList<>();
fieldList.addAll(Arrays.asList(fields));
return fieldList;
}
/**
* java基础类型转el类型
*
* @param varType
* @return
*/
private static String getElasticSearchMappingType(String varType) {
String es;
switch (varType) {
case "date":
es = "date";
break;
case "double":
es = "double";
break;
case "long":
es = "long";
break;
case "int":
es = "long";
break;
default:
es = "text";
break;
}
return es;
}
插入数据
/**
* 插入一条数据
* @param item 需要插入数据的实体类对象
* @param id 该对象的唯一标识(一般为id)
* @return
*/
public Boolean addItem(Item item, String id) {
//单条插入
IndexRequest request = new IndexRequest(INDEX).id(id).source(beanToMap(item));
try {
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
if (response == null) {
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 批量导入数据
* @return
*/
@Override
public Object insertByBulk() {
//批量插入请求
BulkRequest bulkRequest = new BulkRequest();
//从数据库中查询所有数据
List<Item> list = mapper.selectList(null);
//遍历构造bulk条件
for (int i = 0; i < list.size(); i++) {
Item item = list.get(i);
//这里必须每次都使用new IndexRequest(index,type),不然只会插入最后一条记录(这样插入不会覆盖已经存在的Id,也就是不能更新)
bulkRequest.add(new IndexRequest(INDEX).id(item.getId().toString())
.source(BeanMap.create(item)));
}
BulkResponse responses = null;
try {
//客户端返回
responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return JSONObject.toJSON(responses);
}
删除
/**
* 删除一条数据
*
* @param id
* @return
*/
@Override
public boolean deleteOne(Long id) {
//构建delete请求
DeleteRequest request = new DeleteRequest(INDEX).id(id + "");
DeleteResponse response = null;
try {
response = client.delete(request, RequestOptions.DEFAULT);
System.out.println(response);
if (response == null) {
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 删除所有数据
* @return
*/
@Override
public Object deleteAll() {
//构建批量删除请求
DeleteByQueryRequest request = new DeleteByQueryRequest(INDEX);
//匹配所有
request.setQuery(new MatchAllQueryBuilder());
BulkByScrollResponse response = null;
try {
response = client.deleteByQuery(request, RequestOptions.DEFAULT);
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
return JSONObject.toJSON(response);
}
/**
* 批量删除
* @param ids
* @return
*/
@Override
public Object deleteList(List<String> ids){
//构建批量删除请求
DeleteByQueryRequest request = new DeleteByQueryRequest(INDEX);
IdsQueryBuilder queryBuilder = new IdsQueryBuilder();
for(String id : ids){
queryBuilder.addIds(id);
}
//匹配所有
request.setQuery(queryBuilder);
BulkByScrollResponse response = null;
try {
response = client.deleteByQuery(request, RequestOptions.DEFAULT);
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
return JSONObject.toJSON(response);
}
修改
public Object updateItem(Item item){
UpdateRequest updateRequest = new UpdateRequest(INDEX, item.getId().toString());
updateRequest.doc(beanToMap(item));
try {
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
if(updateResponse!=null && updateResponse.status().getStatus() == 200){
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
查询
/**
* 查询所有
* @return
*/
public Page<Item> selectAll() {
List<Item> result = new ArrayList<>();
Page<Item> page = new Page<>();
//构造请求
SearchRequest request = new SearchRequest(INDEX);
//搜索条件构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//matchAllQuery查询所有
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
//分页(查询所有可不要)
searchSourceBuilder.from(1);
searchSourceBuilder.size(1000);
request.source(searchSourceBuilder);
try {
//返回结果封装
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
page.setSize(1000);
page.setCurrent(1);
page.setTotal(hits.length);
for (SearchHit hit : hits) {
Item item = JSONObject.parseObject(hit.getSourceAsString(), Item.class);
result.add(item);
}
page.setRecords(result);
} catch (IOException e) {
e.printStackTrace();
}
return page;
}
/**
* 基本查询 带高亮显示
* @param key 字段名称
* @param value 查询值
* @param current 当前页
* @param size 每页显示条数
* @return
*/
public Object selectByKey(String key, String value, Integer current, Integer size) {
//分页返回结果
Page<Item> page = new Page<>();
//构建搜索请求
SearchRequest request = new SearchRequest(INDEX);
//搜索条件构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//设置高亮显示
HighlightBuilder hiBuilder = new HighlightBuilder();
//设置高亮标签
hiBuilder.preTags("<a style='color: #e4393c'>");
hiBuilder.postTags("</a>");
//高亮字段
hiBuilder.field(key);
/**
* 使用QueryBuilder
* termQuery("key", obj) 完全匹配
* termsQuery("key", obj1, obj2..) 一次匹配多个值
* matchQuery("key", Obj) 单个匹配, field不支持通配符, 前缀具高级特性
* multiMatchQuery("text", "field1", "field2"..); 匹配多个字段, field有通配符忒行
*/
//搜索条件 matchQuery
searchSourceBuilder.query(QueryBuilders.matchQuery(key, value));
//搜索结果分页 当前页
searchSourceBuilder.from(current);
//每页显示条数
searchSourceBuilder.size(size);
//高亮显示添加到构造器(不需要高亮显示则不添加)
searchSourceBuilder.highlighter(hiBuilder);
//构造器添加到搜索请求
request.source(searchSourceBuilder);
//客户端返回信息
SearchResponse response = null;
try {
//请求返回
response = client.search(request, RequestOptions.DEFAULT);
if (response == null) {
return page;
}
} catch (IOException e) {
e.printStackTrace();
}
//搜索结果
SearchHit[] hits = response.getHits().getHits();
//分页封装
page.setSize(size);
page.setCurrent(current);
page.setTotal(hits.length);
//所有结果封装
List<Item> list = new ArrayList<>();
//高亮字段封装
for (SearchHit hit : hits) {
//设置高亮字段
Item item = JSONObject.parseObject(hit.getSourceAsString(), Item.class);
String title = hit.getHighlightFields().get(key).getFragments()[0].toString();
item.setTitle(title);
//返回结果
list.add(item);
}
page.setRecords(list);
return page;
}
/**
* 查询建议 例如你输入小米,会提示你小米手机、小米电视、小米平板等,前提相应数据应该添加到el中
* @param value
* @return
*/
public Object selectSuggest(String value) {
List<String> result = new ArrayList<>();
//搜索条件
SearchRequest request = new SearchRequest(INDEX);
//搜索条件构造器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/**
* PhraseSuggestionBuilder (org.elasticsearch.search.suggest.phrase)
* CompletionSuggestionBuilder (org.elasticsearch.search.suggest.completion)
* TermSuggestionBuilder (org.elasticsearch.search.suggest.term)
*/
//提示搜索构造器, 使用PhraseSuggestionBuilder 构造器参数为搜索字段
//PhraseSuggestionBuilder suggestionBuilder = new PhraseSuggestionBuilder("title");
//搜索内容
//suggestionBuilder.text(value);
//提示搜索构造器, 使用TermSuggestionBuilder 构造器参数为搜索字段
//TermSuggestionBuilder suggestionBuilder = new TermSuggestionBuilder("title");
//suggestionBuilder.text(value);
//提示搜索构造器, 使用CompletionSuggestionBuilder 构造器参数为搜索字段
//如果使用使用CompletionSuggestionBuilder,需要在构建mapping时添加相对应的suggest
CompletionSuggestionBuilder suggestionBuilder = new CompletionSuggestionBuilder("title.suggest");
suggestionBuilder.text(value);
SuggestBuilder suggestBuilder = new SuggestBuilder();
//添加Suggestion 第一个参数为搜索名称,可以随便打,与下面的搜索结果名称匹配即可 第二个参数为提示搜索构造器
suggestBuilder.addSuggestion("my-suggest", suggestionBuilder);
searchSourceBuilder.suggest(suggestBuilder);
request.source(searchSourceBuilder);
try {
//返回内容
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//保存es返回结果 my-suggest需要与上面的搜索结果名称相同
List<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> list = response
.getSuggest().getSuggestion("my-suggest").getEntries();
//返回内容格式可自行打断点查看,这里直接封装使用
if (list == null) {
return result;
} else {
//转为list保存结果字符串
for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> e : list) {
for (Suggest.Suggestion.Entry.Option option : e) {
result.add(option.getText().toString());
System.out.println(option.getText().toString());
}
}
}
System.out.println(result);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
后记
通过以上的基本增删改查我们可以发现,java操作elasticsearch步骤其实是固定的,第一步都是首先获取相对应的连接,之后是添加构造器,最后发送请求即可。因此如果想要熟练的使用java操作elasticsearch,就必须先掌握elasticsearch的RESTful请求。
举个栗子:
如果我们想要使用kibana查询一条数据,我们需要发送的请求为:
POST localhost:9200/item/_update/1
这其中我们知道,首先这是一个post请求,后面是ip与端口号,再后面是索引,再后面是_update更新请求,最后是id=1,因为我们在Java中也是同样。我们首先需要构建一个修改请求:
UpdateRequest updateRequest = new UpdateRequest(INDEX, item.getId().toString());
之后我们添加需要修改的文档doc:
updateRequest.doc(beanToMap(item));
最后我们通过客户端发送请求获得回应即可:
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
elasticsearch虽然没有官方的java操作文档,但它的jar包源码命名都非常明朗,对应的增删改查的请求就是基本的IndexRequest,DeleteRequest,UpdateRequest,SearchRequest,批量的增删改查也有相应的queryRequest,只要调用相应的客户端即可。
最后附上项目的码云地址,就是非常基础的一个springboot项目,如果有不懂的小伙伴,可以留言,有更好的意见及建议的也可以告诉我,大家一起进步。
https://gitee.com/hzy_skk/elasticsearch.git