Java操作ElasticSearch有两种方式:
1)ElasticSearch提供的客户端API
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>7.6.2</version>
</dependency>
2)Spring Data ElasticSearch
使用Spring Data 下二级子项目Spring Data Elasticsearch进行操作。支持POJO方法操作Elasticsearch。相比Elasticsearch提供的API更加简单更加方便。
在这里我们使用SpringData
1、SpringData ElasticSearch 项目环境搭建
1-1 创建项目
1-2 修改pom.xml
使用spring-boot-starter-parent版本为2.3.3.RELEASE,对应spring-data-elasticsearch版本为4.0.3
在项目pom.xml中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
1-3 修改配置文件
在老版本中通过9300内部端口访问。通过TransportClient进行访问的。
从Elasticsearch 8.x开始要放弃Transport。
所以从Spring Data Elasticsearch 4.0 开始都是基于Rest进行访问。
spring:
elasticsearch:
rest:
uris: http://192.168.163.12:9200
1-4 新建索引和指定mapping
package com.alan.es.pojo;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* 商品实体类
* 自定义mapping关系是通过实体类进行控制的
* @Document 注解 org.springframework.data.elasticsearch.annotations
* 定义类和索引的关系
*/
@Document(indexName = "index_item",shards = 1,replicas = 1)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
/**
* 在SpringData ES中要求所有的属性都必须要有对应注解,否则自动映射
*/
@Id
//ID起名“id”和“_id” 都可以
private String id;
/*
非主键都用Field注解
如果希望Document和实体类中都叫做 title,那么 @Field(value = "")
value不用填值,如果希望名字不一样,那就需要填值
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Long)
private Long price;
@Field(type = FieldType.Keyword)
private String catName;
}
@Document指定实体类和索引对应关系
indexName:索引名称
type: 索引类型(从ES 7.0 开始已经过时了)
shards: 主分片数量。从ES 7开始默认1
replicas:复制分片数量。从ES 7开始默认1
@Id 指定主键
@Field指定普通属性
type: 对应Elasticsearch中属性类型。使用FiledType枚举可以快速获取。测试发现没有type属性可能出现无法自动创建类型问题,所以一定要有type属性。
text类型能被分词
keywords不能被分词
index: 是否创建索引。作为搜索条件时index必须为true
analyzer:指定分词器类型。
2、 ElasticsearchTemplate的使用
2-1 初始化索引
@SpringBootTest
class EsDemoApplicationTests {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Test
void contextLoads() {
//调用代码,实现映射关系
// SpringData 4.x操作索引都是通过IndexOptions进行操作
IndexOperations operations = elasticsearchRestTemplate.indexOps(Item.class);
//创建索引
operations.create();
//createMapping 根据实体类获取映射关系
//putMapping 把映射关系添加到索引中
Document mapping = operations.createMapping();
boolean b = operations.putMapping(mapping);
System.out.println("索引创建:" + b);
}
}
2-2 删除索引
/**
* 删除索引
*/
@Test
void deleteIndex(){
IndexOperations operations = elasticsearchRestTemplate.indexOps(Item.class);
operations.delete();
}
2-3 添加文档
如果索引和类型不存在,也可以执行新增,新增后自动创建索引和类型。但是field通过动态mapping进行映射,ElasticSearch根据值类型进行判断每个属性类型,默认每个属性都是standard分词器,ik分词器是不生效的。所以一定要先通过diamante进行初始化或者直接通过命令创建所有field的mapping。
2-3-1 新增单个文档
如果对象的id属性没有赋值,让ES自动生成主键,存储时ID属性没有值,_Id存储document的主键值。
如果存储对象的id属性明确设置了值,存储时id属性为设置的值,ES中document对象的_id也是设置的值。
/**
* 新增单个文档
*/
@Test
void addDocument(){
Item item = new Item("id", "标题", 1110000l, "catName");
Item save = elasticsearchRestTemplate.save(item);
System.out.println("新增单个文档:"+save);
}
/**
* 新增单个文档(不指定ID)
*/
@Test
void addDocument(){
Item item = new Item(null, "标题", 1110000l, "catName");
Item save = elasticsearchRestTemplate.save(item);
System.out.println("新增单个文档:"+save);
}
//输出:新增单个文档:Item{id='BiAkpIAB5cT0lDi1vRvj', title='标题', price=1110000, catName='catName'}
2-3-2 批量新增
/**
* 批量新增文档
*/
@Test
void addDocuments(){
List<Item> list = new ArrayList<>();
list.add(new Item("id0000002","标题2",12l,"catName2"));
list.add(new Item("id0000003","标题3",13l,"catName3"));
list.add(new Item("id0000004","华为手机",4999l,"华为手机!功能牛逼!"));
list.add(new Item("id0000005","小米手机",4999l,"小米手机,为发烧而生!"));
Iterable<Item> save = elasticsearchRestTemplate.save(list);
System.out.println("批量新增文档:"+save);
}
2-4 删除文档
/**
* 删除文档
*/
@Test
void deleteDocument(){
String id = elasticsearchRestTemplate.delete("id", Item.class);
System.out.println("删除文档:"+id);
}
2-5 修改文档
修改的API就是新增的API,只要保证主键ID是已存在的ID,那么新增就是修改
2-6 查询
2.6-1 根据主键查询
/**
* 根据主键查询
*/
@Test
void searchById(){
Item res = elasticsearchRestTemplate.get("id0000002", Item.class);
System.out.println("res:"+res);
}
2-6-2 模糊查询
/**
* 模糊查询
* 去所有field中查询指定条件。
*/
@Test
void search(){
QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery("标题");
Query query = new NativeSearchQuery(queryBuilder);
// 相当于最外层hits
SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query,Item.class);
// 里层的hits
List<SearchHit<Item>> list = searchHits.getSearchHits();
// 进行转换
List<Item> listResult = new ArrayList<>();
list.forEach(sh -> {
listResult.add(sh.getContent());
});
System.out.println(listResult);
}
2-6-3 使用match_all 查询所有文档
/**
* 使用match_all 查询所有文档
*/
@Test
void matchAllSearch(){
Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());
SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);
List<Item> listRes = new ArrayList<>();
searchHits.forEach(sh->{
listRes.add(sh.getContent());
});
System.out.println("res:"+listRes);
}
2-6-4 使用match查询文档
/**
* 使用match 查询文档
*/
@Test
void matchSearch(){
Query query = new NativeSearchQuery(QueryBuilders.matchQuery("title","华为"));
SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);
List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
List<Item> res = new ArrayList<>();
searchHitList.forEach(sh->{
res.add(sh.getContent());
});
System.out.println("res:"+res);
}
2-6-5 使用 match_phrase 查询文档
短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。
如果属性使用ik分词器,从分词后的索引数据进行匹配。
/**
* 短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。
* 如果属性使用ik分词器,从分词后的索引数据进行匹配。
*/
@Test
void matchPhrase(){
Query searchQuery = new NativeSearchQuery(QueryBuilders.matchPhraseQuery("title","手机"));
SearchHits<Item> searchHits = elasticsearchRestTemplate.search(searchQuery, Item.class);
List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
List<Item> res = new ArrayList<>();
searchHitList.forEach(sh->{
res.add(sh.getContent());
});
System.out.println("res:"+res);
}
2-6-6 使用range查询文档
@Test
void range(){
Query searchQuery = new NativeSearchQuery(QueryBuilders.rangeQuery("price").gte(100).lte(5000));
SearchHits<Item> searchHits = elasticsearchRestTemplate.search(searchQuery, Item.class);
List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
List<Item> res = new ArrayList<>();
searchHitList.forEach(sh->{
res.add(sh.getContent());
});
System.out.println("res:"+res);
}
2-6-7 多条件查询
@Test
void mustShould(){
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
List<QueryBuilder> listQuery = new ArrayList<>();
listQuery.add(QueryBuilders.matchPhraseQuery("title","手机"));
listQuery.add(QueryBuilders.rangeQuery("price").gte(1000).lte(6000));
// boolQueryBuilder.should().addAll(listQuery);// ||
boolQueryBuilder.must().addAll(listQuery);// &&
Query query = new NativeSearchQuery(boolQueryBuilder);
SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);
List<Item> res = new ArrayList<>();
for (SearchHit<Item> searchHit : search.getSearchHits()) {
res.add(searchHit.getContent());
}
System.out.println("res:"+res);
}
2-6-8 分页与排序
/**
* 分页与排序
*/
@Test
void PageSort(){
Query query = new NativeSearchQuery(QueryBuilders.matchAllQuery());
query.setPageable(PageRequest.of(0,2));
query.addSort(Sort.by(Sort.Direction.DESC,"price"));
SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);
for (SearchHit<Item> searchHit : search.getSearchHits()) {
System.out.println("res:"+searchHit.getContent());
}
}
如果实体类中主键只有@Id注解,String id对应ES中是text类型,text类型是不允许被排序,所以如果必须按照主键进行排序时需要在实体类中设置主键类型
2-6-9 高亮显示
@Test
void highLight(){
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(QueryBuilders.matchQuery("catName", "手机"));
// 排序
nativeSearchQuery.addSort(Sort.by(Sort.Direction.DESC,"price"));
// 分页
nativeSearchQuery.setPageable(PageRequest.of(0,2));
// 设置高亮条件
HighlightBuilder hlBuilder = new HighlightBuilder();
// 哪个属性高亮
hlBuilder.field("catName");
// 高亮内容前缀
hlBuilder.preTags("<span style='color:red'>");
// 高亮内容后缀
hlBuilder.postTags("</span>");
// 高亮查询
HighlightQuery hlQuery = new HighlightQuery(hlBuilder);
// 应用高亮
nativeSearchQuery.setHighlightQuery(hlQuery);
// 外层hits
SearchHits<Item> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Item.class);
// 查询出的总条数
System.out.println(searchHits.getTotalHits());
// 里层hits
List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
List<Item> list = new ArrayList<>();
searchHitList.forEach(sh ->{
// 取出非高亮数据
Item peo = sh.getContent();
// 获取高亮数据
String hlContent = sh.getHighlightField("catName").get(0);
// 用高亮数据替换非高亮数据
peo.setCatName(hlContent);
list.add(peo);
});
System.out.println(list);
}
本文正在参加「技术专题19期 漫谈数据库技术」活动