上篇文章我们讲解了elasticsearch的安装,这次我们来搞一下,如何在自己的项目中集成elasticsearch。 正常来讲spring-data中都会提供相应的starter,让我们方便的使用各种Template操作对应的组件,比如常用RedisTemplate, JdbcTemplate等,其实spring-data中也提供的相应的elasticsearch的对应工具。但是我这里并没有使用,而是直接使用的elasticsearch原生api实现的。为什么这么做呢,因为spring-data-elasticsearch 最新的版本3.2,最高支持的elasticsearch版本为6.8, 而我们用的是7.2的版本,并且官方建议我们使用的jar版本最好和软件版本一致。
还有一个问题, 是关于客户端的, spring-data-elasticsearch中默认使用的是TransportClient, 这个客户端在7这个版本中已经不再建议使用了,并且将会在8的版本中彻底移除。而我们用的是7这个版本,目前推荐使用的elasticsearch的高级客户端,HighLevelRestClient. spring-data-es中声明会一直支持TransportClient,只要你的这个es版本支持。当然,spring-data-es中也是支持高级别客户端的,但是还有由于支持版本过低的问题,所以我最后还是决定采用原生客户端。如果大家用的es版本比较低,还是可以使用spring-data-es的。
接下来我们来集成项目,集成之前,大家需要了解一下es中的一些专有名词,比如什么是索引,类型,文档,同时你要了解es是干什么用的。es最主要的功能就是查询,也就是他查东西的速度非常快,并且支持分词,全文检索。如果我们在mysql中查询一遍文章的内容,其实是非常痛苦的,我们可能必须得使用 like 或者拼接or去查询多个字段,并且有些场景是无法实现的,比如你的文章中的内容中包含 ”一朵鲜花“, 而你去搜索 ”一朵花“ 这种情况你是查不到的,但是es可以,因为es可以分词, 他会一朵鲜花, 分成 ”一朵“ ”鲜花“ 两个词,再把 ”一朵花“ 分成 ”一朵“ 和 ”花“ (注: 这里是个人方便理解,可能具体分词不是这么分的,大家领悟精髓)。 就很容易做到查询。 同时es查询的比较快,也是因为他的内部采用了倒叙索引,关于倒叙索引的原理,大家可以去找找资料,这里就不展开说了。
一。 引入jar包
代码语言:javascript复制 <dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.2.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.2.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.2.0</version>
</dependency>
二。封装工具类,这里主要使用高级别客户端封装, 主要封装了创建索引,判断索引是否存在,删除索引, 插入文档的功能,还有一些高级功能还没有 研究完,比如高亮和分页,我会一边研究一边更新,先给出一些简单的操作demo.后续文章我们在深入展开。
代码语言:javascript复制@Component
@Slf4j
public class EsUtil {
@Resource
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引(默认分片数为5和副本数为1)
* @param indexName
* @throws IOException
*/
public boolean createIndex(String indexName) throws IOException {
CreateIndexRequest request = new CreateIndexRequest(indexName);
request.settings(Settings.builder()
// 设置分片数为3, 副本为2
.put("index.number_of_shards", 3)
.put("index.number_of_replicas", 2)
);
request.mapping(generateBuilder());
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
// 指示是否所有节点都已确认请求
boolean acknowledged = response.isAcknowledged();
// 指示是否在超时之前为索引中的每个分片启动了必需的分片副本数
boolean shardsAcknowledged = response.isShardsAcknowledged();
if (acknowledged || shardsAcknowledged) {
log.info("创建索引成功!索引名称为{}", indexName);
return true;
}
return false;
}
/**
* 判断索引是否存在
* @param indexName
* @return
*/
public boolean isIndexExists(String indexName){
boolean exists = false;
try {
GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
getIndexRequest.humanReadable(true);
exists = restHighLevelClient.indices().exists(getIndexRequest,RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return exists;
}
/**
* 删除索引
* @param indexName
* @return
*/
public boolean delIndex(String indexName){
boolean acknowledged = false;
try {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
deleteIndexRequest.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN);
AcknowledgedResponse delete = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
acknowledged = delete.isAcknowledged();
} catch (IOException e) {
e.printStackTrace();
}
return acknowledged;
}
/**
* 更新索引(默认分片数为5和副本数为1):
* 只能给索引上添加一些不存在的字段
* 已经存在的映射不能改
*
* @param clazz 根据实体自动映射es索引
* @throws IOException
*/
public boolean updateIndex(Class clazz) throws Exception {
Document declaredAnnotation = (Document )clazz.getDeclaredAnnotation(Document.class);
if(declaredAnnotation == null){
throw new Exception(String.format("class name: %s can not find Annotation [Document], please check", clazz.getName()));
}
String indexName = declaredAnnotation.index();
PutMappingRequest request = new PutMappingRequest(indexName);
request.source(generateBuilder(clazz));
AcknowledgedResponse response = restHighLevelClient.indices().putMapping(request, RequestOptions.DEFAULT);
// 指示是否所有节点都已确认请求
boolean acknowledged = response.isAcknowledged();
if (acknowledged ) {
log.info("更新索引索引成功!索引名称为{}", indexName);
return true;
}
return false;
}
/**
* 添加单条数据
* 提供多种方式:
* 1. json
* 2. map
* Map<String, Object> jsonMap = new HashMap<>();
* jsonMap.put("user", "kimchy");
* jsonMap.put("postDate", new Date());
* jsonMap.put("message", "trying out Elasticsearch");
* IndexRequest indexRequest = new IndexRequest("posts")
* .id("1").source(jsonMap);
* 3. builder
* XContentBuilder builder = XContentFactory.jsonBuilder();
* builder.startObject();
* {
* builder.field("user", "kimchy");
* builder.timeField("postDate", new Date());
* builder.field("message", "trying out Elasticsearch");
* }
* builder.endObject();
* IndexRequest indexRequest = new IndexRequest("posts")
* .id("1").source(builder);
* 4. source:
* IndexRequest indexRequest = new IndexRequest("posts")
* .id("1")
* .source("user", "kimchy",
* "postDate", new Date(),
* "message", "trying out Elasticsearch");
*
* 报错: Validation Failed: 1: type is missing;
* 加入两个jar包解决
*
* @return
*/
public IndexResponse add(String indexName, Object o) throws IOException {
IndexRequest request = new IndexRequest(indexName);
String userJson = JSON.toJSONString(o);
request.source(userJson, XContentType.JSON);
IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
return indexResponse;
}
private XContentBuilder generateBuilder() throws IOException {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
{
builder.startObject("properties");
{
// es7及以后去掉了映射类型--person
builder.startObject("name");
{
builder.field("type", "text");
builder.field("analyzer", "ik_smart");
}
builder.endObject();
}
{
builder.startObject("age");
{
builder.field("type", "integer");
}
builder.endObject();
}
{
builder.startObject("desc");
{
builder.field("type", "text");
builder.field("analyzer", "ik_smart");
}
builder.endObject();
}
{
builder.startObject("id");
{
builder.field("type", "integer");
}
builder.endObject();
}
builder.endObject();
}
builder.endObject();
/*.startObject().field("properties")
.startObject().field("person")
.startObject("name")
.field("type" , "text")
.field("analyzer", "ik_smart")
.endObject()
.startObject("age")
.field("type" , "int")
.endObject()
.startObject("desc")
.field("type", "text")
.field("analyzer", "ik_smart")
.endObject()
.endObject()
.endObject();*/
return builder;
}
}
上面工具类中给出的索引结构是一个用户,只有id, name , age, desc 四个简单字段的结构
同时desc字段和姓名字段都是使用的ik-smart做的分词。
接下来大家就可以使用controller或者junittest来进行调用, 配合head插件观察数据。 整体的大致流程就是, index定义索引结构,然后我们把按格式数据存到es中, 使用es提供的高效api来做查询。 这篇文章先到这里,其实这里有一个痛点就是如果我们的数据结构比较复杂, 那么我们在创建索引的时候可能需要写出大量的代码,四个字段就这么多
所以这里其实我们可以根据实体的结构自动设计索引结构,像spring-data-es中就是根据我们在实体类上的注解,自动创建索引的。我这里也实现了自定义注解来创建es索引结构的方法,下一篇文章给大家介绍一下。