Spring Data Elasticsearch
使用Spring Data 下二级子项目Spring Data Elasticsearch进行操作。支持POJO方法操作Elasticsearch。相比Elasticsearch提供的API更加简单更加方便。
Spring Data Elasticsearch项目环境搭建
创建项目
以上项目是一个空项目,什么依赖都没有添加
添加依赖
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>SpringDateEs</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其中和es相关的就是一个依赖,以后在项目里面想要使用java操作es,那么就添加这个依赖就可以了
代码语言:javascript复制 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
编写配置文件
代码语言:javascript复制spring:
data:
elasticsearch:
cluster-name: docker-cluster
cluster-nodes: 192.168.40.145:9300
配置文件中cluster-name的值如何而来?这个是es集群的名字 浏览器输入地址之后就出现了
cluster-nodes 的值就是es在哪个虚拟机上面,那么就是哪个虚拟机的ip地址,我们创建es容器的时候,有两个端口号,一个是9200,一个是9300,9200是浏览器访问es的端口号,9300是java项目访问es需要写的端口号,所以,在我们的项目里面,需要写的是9300
在测试类里面写测试的方法来操作es
代码语言:javascript复制 @Autowired
private ElasticsearchTemplate elasticsearchTemplate;
只要注入以上的这个,那么就可以使用elasticsearchTemplate操作es了
ElasticsearchTemplate的使用
1 创建实体
代码语言:javascript复制@Document指定实体类和索引对应关系
indexName:索引名称 写了这个之后,有就用,没有就创建这个索引
type: 索引类型
shards: 主分片数量
replicas:复制分片数量
@Id 指定主键
@Field指定普通属性
type: 对应Elasticsearch中属性类型。使用FiledType枚举可以快速获取。测试发现没有type属性可能出现无法自动创建类型问题,所以一定要有type属性。
text类型能被分词
keywords不能被分词
index: 是否创建索引。作为搜索条件时index必须为true
analyzer:指定分词器类型。
@Document(indexName = "people_index",type = "people_type",shards = 1,replicas = 1)
public class People {
@Id
private String id;
// 整个name不被分词,切不创建索引
// Keyword表示不被分词
@Field(type= FieldType.Keyword,index = false)
private String name;
// address被ik分词
// Text类型的属性才能被分词
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String address;
@Field(type = FieldType.Long,index = false)
private int age;
2 初始化索引(相当于创建数据库)
根据实体类上面的注解写的东西,我们要创建索引,相当于我们要创建一个数据库,在测试类里面写:
代码语言:javascript复制elasticsearchTemplate有很多的方法,其中createIndex()就是创建一个索引,只是创建一个数据库,里面的字段是什么类型,也就是在实体类里面的注解上面的那些字段的类型如何放到索引中,需要putMapping()方法。
也就是在创建索引的时候,createIndex(),putMapping() 都是要有的,只要写了这两个方法,那么在es里面就创建了一个索引了
代码语言:javascript复制 @Test
void contextLoads() {
// 根据实体类创建索引,
boolean result1 = elasticsearchTemplate.createIndex(People.class);
System.out.println(result1);
// 将索引放到软件里面
boolean results = elasticsearchTemplate.putMapping(People.class);
System.out.println(results);
}
以上在实体里面的注解里面的索引的 名字是people_index_16 所以我们现在要创建的索引,执行代码
他们返回的值是
返回true,说明创建索引成功,我们可以在kibana里面查看
有这个索引,相当于有数据库了,只是里面没有数据
3 删除索引(删除数据库)
代码语言:javascript复制@Test
void delete(){
boolean result = elasticsearchTemplate.deleteIndex(People.class);
System.out.println(result);
}
执行完上面的代码,我们在kibana里面查看索引在不在了?
相当于删除数据库了
4 添加文档 (往数据库添加数据)
如果索引和类型不存在,也可以执行进行新增,新增后自动创建索引和类型。但是field通过动态mapping进行映射,elaticsearch根据值类型进行判断每个属性类型,默认每个属性都是standard分词器,ik分词器是不生效的。所以一定要先通过代码进行初始化或直接在elasticsearch中通过命令创建所有field的mapping
4.1 新增单个文档(新增一条数据)
如果对象的id属性没有赋值,让ES自动生成主键,存储时id属性没有值,_id存储document的主键值。 如果对象的id属性明确设置值,存储时id属性为设置的值,ES中document对象的_id也是设置的值。
代码语言:javascript复制@Test
void insert1(){
创建要往es中添加的数据的实体类
People peo = new People();
peo.setId("123");
peo.setName("张三");
peo.setAddress("北京市海淀区回龙观东大街");
peo.setAge(18);
IndexQuery query = new IndexQuery();
query.setObject(peo);
index() 方法就是新增的方法,只是里面的参数的类型是IndexQuery,所以要创建IndexQuery对象
String result = elasticsearchTemplate.index(query);
System.out.println(result);
}
新增成功,返回的是数据的id值
我们从kibana里面看看有没有新增数据
4.2 批量新增(新增多条数据)
下面代码中使用的IndexQueryBuilder()进行构建,可以一行代码完成。也可以使用上面的IndexQuery()。效果是完全相同的,只是需要写多行。
代码语言:javascript复制@Test
void bulk(){
List<IndexQuery> list = new ArrayList<>();
// IndexQuery多行写法
IndexQuery indexQuery = new IndexQuery();
indexQuery.setObject(new People("1","王五","北京东城",12));
list.add(indexQuery);
// IndexQuery 连缀写法
list.add(new IndexQueryBuilder().withObject(new People("2","赵六","北京西城",13)).build());
list.add(new IndexQueryBuilder().withObject(new People("3","吴七","北京昌平",14)).build());
elasticsearchTemplate.bulkIndex(list);
}
执行完上面的代码,我们看看kibana里面
以上es里面确实有很多的数据了。
5 删除操作(删除数据)
根据主键删除
代码语言:javascript复制delete(String indexName,String typeName,String id); 通过字符串指定索引,类型和id值
delete(Class,String id)
第一个参数传递实体类类类型,建议使用此方法,
减少索引名和类型名由于手动编写出现错误的概率。
返回值为delete方法第二个参数值(删除文档的主键值)
代码语言:javascript复制@Test
void deleteDoc(){
删除id为4的数据
String result = elasticsearchTemplate.delete(People.class, "4");
System.out.println(result);
}
按照条件删除
代码语言:javascript复制DeleteQuery deleteQuery = new DeleteQuery();
deleteQuery.setQuery(new MatchQueryBuilder("desc","学生"));
elasticsearchTemplate.delete(deleteQuery,People.class);
6 修改操作(修改数据)
修改操作就是新增代码,只要保证主键id已经存在,新增就是修改
7 查询操作(查询数据)
7.1 模糊查询
去所有field中查询指定条件。
代码语言:javascript复制@Test
void query(){
// NativeSearchQuery构造方法参数。
// 北京去和所有field进行匹配,只要出现了北京就可以进行查询
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("北京");
// 查询条件SearchQuery是接口,只能实例化实现类。
SearchQuery searchQuery = new NativeSearchQuery(queryStringQueryBuilder);
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
7.2 使用match_all查询所有文档
代码语言:javascript复制@Test
void matchAll(){
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchAllQuery());
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
7.3 使用match查询文档
代码语言:javascript复制@Test
void match(){
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchQuery("address","我要去北京"));
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
7.4使用match_phrase查询文档
短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词。 如果属性使用ik分词器,从分词后的索引数据中进行匹配。
代码语言:javascript复制@Test
void mathPhrase(){
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchPhraseQuery("address","北京市"));
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
7.5使用range查询文档
代码语言:javascript复制@Test
void range(){
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.rangeQuery("age").gte(22).lte(23));
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
7.6多条件查询
代码语言:javascript复制 @Test
void MustShould(){
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
List<QueryBuilder> listQuery = new ArrayList<>();
listQuery.add(QueryBuilders.matchPhraseQuery("name","张三"));
listQuery.add(QueryBuilders.rangeQuery("age").gte(22).lte(23));
// boolQueryBuilder.should().addAll(listQuery); // 逻辑或
boolQueryBuilder.must().addAll(listQuery); // 逻辑与
SearchQuery searchQuery = new NativeSearchQuery(boolQueryBuilder);
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
7.7分页与排序
代码语言:javascript复制@Test
void PageSort(){
SearchQuery searchQuery = new NativeSearchQuery(QueryBuilders.matchAllQuery());
// 分页 第一个参数是页码,从0算起。第二个参数是每页显示的条数
searchQuery.setPageable(PageRequest.of(0,2));
// 排序 第一个参数排序规则 DESC ASC 第二个参数是排序属性
searchQuery.addSort(Sort.by(Sort.Direction.DESC,"age"));
List<People> list = elasticsearchTemplate.queryForList(searchQuery, People.class);
for(People people : list){
System.out.println(people);
}
}
如果实体类中主键只有@Id注解,String id对应ES中是text类型,text类型是不允许被排序,所以如果必须按照主键进行排序时需要在实体类中设置主键类型
代码语言:javascript复制@Id
@Field(type = FieldType.Keyword)
private String id;
7.8高亮查询
代码语言:javascript复制 @Test
void hl() {
创建高亮的实体类对象
// 高亮属性
HighlightBuilder.Field hf = new HighlightBuilder.Field("address");
// 高亮前缀
hf.preTags("<span>");
// 高亮后缀
hf.postTags("</span>");
创建查询对象
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
// 排序
.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC))
// 分页
.withPageable(PageRequest.of(0, 2))
// 应用高亮
.withHighlightFields(hf)
// 查询条件, 必须要有条件,否则高亮
.withQuery(QueryBuilders.matchQuery("address", "北京市")).build();
第三个参数 new SearchResultMapper() 就是自定义映射的结果
AggregatedPage<People> peoples = elasticsearchTemplate.queryForPage(searchQuery, People.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
/*
{
"took" : 190,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.7918377,
"hits" : [
{
"_index" : "people_index",
"_type" : "people_type",
"_id" : "1",
"_score" : 1.7918377,
"_source" : {
"id" : "1",
"name" : "张三",
"address" : "北京市回龙观东大街",
"age" : 21
},
"highlight" : {
"address" : [
"<span>北京</span><span>市</span>回龙观东大街"
]
}
},
{
"_index" : "people_index",
"_type" : "people_type",
"_id" : "2",
"_score" : 1.463562,
"_source" : {
"id" : "2",
"name" : "李四",
"address" : "北京市大兴区科创十四街",
"age" : 22
},
"highlight" : {
"address" : [
"<span>北京</span><span>市</span>大兴区科创十四街"
]
}
},
{
"_index" : "people_index",
"_type" : "people_type",
"_id" : "3",
"_score" : 0.38845786,
"_source" : {
"id" : "3",
"name" : "王五",
"address" : "天津市北京大街",
"age" : 23
},
"highlight" : {
"address" : [
"天津市<span>北京</span>大街"
]
}
}
]
}
}
*/
// 取最里面层的hits
SearchHit[] searchHits = searchResponse.getHits().getHits();
// 最终返回的数据
List<T> peopleList = new ArrayList<>();
for (SearchHit searchHit : searchHits) {
//需要把SearchHit转换为People
People peo = new People();
// 取出非高亮数据
Map<String, Object> mapSource = searchHit.getSourceAsMap();
// 取出高亮数据
Map<String, HighlightField> mapHL = searchHit.getHighlightFields();
// 把非高亮数据进行填充
peo.setId(mapSource.get("id").toString());
peo.setName(mapSource.get("name").toString());
peo.setAge(Integer.parseInt(mapSource.get("id").toString()));
// 判断是否有高亮,如果只有一个搜索条件,一定有这个高亮数据,if可以省略
if (mapHL.containsKey("address")) {
// 设置高亮数据
peo.setAddress(mapHL.get("address").getFragments()[0].toString());
}
// 把people添加到集合中
peopleList.add((T) peo);
}
// 如果没有分页,只有第一个参数。
// 总条数在第一个hits里面。
return new AggregatedPageImpl<>(peopleList, pageable, searchResponse.getHits().totalHits);
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
return null;
}
});
// 通过getContent查询出最终List<People>
for (People people : peoples.getContent()) {
System.out.println(people);
}
}