一、spring-data-Elasticsearch
目前市面上有两类客户端
一类是TransportClient 为代表的ES原生客户端,不能执行原生dsl语句必须使用它的Java api方法。
另外一种是以Rest Api为主的missing client,最典型的就是jest。 这种客户端可以直接使用dsl语句拼成的字符串,直接传给服务端,然后返回json字符串再解析。
两种方式各有优劣,但是最近elasticsearch官网,宣布计划在7.0以后的版本中废除TransportClient。以RestClient为主。
RestClient: jest, java low level client,java high level client
springboot又提供了 EsRestTemplate 简化了es操作,底层使用了java high level client实现
还提供了EsRepostitory
由于原生的Elasticsearch客户端API非常麻烦(Elasticsearch Clients | Elastic)。所以这里直接用Spring提供的套件:Spring Data Elasticsearch。
Spring Data Elasticsearch - Reference Documentation
spring-data-Elasticsearch 使用之前,必须先确定版本,elasticsearch 对版本的要求比较高。
每个springboot场景启动器(引入了一组依赖并且配置了版本号的maven项目)
项目开发时会引入多个场景启动器,如果多个场景器引入了 同一个依赖但是版本不同,由于maven自动解决依赖冲突会按照先声明者路径短者优先的原则 挑选一个版本使用。没有被用到的版本的依赖 如果项目代码中使用了它的新特性会导致代码运行错误
拓展: springboot和elasticsearch的版本对照:
1.引入依赖:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wbm</groupId>
<artifactId>es-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 如果不手动添加如下依赖,可能报错: Failed to resolve org.junit.platform:junit-platform-launcher:1.6.3 -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.在application.properties中添加配置
# 单机情况下
spring.elasticsearch.rest.uris=http://192.168.1.170:9200
# 集群情况下
#spring.elasticsearch.rest.uris[0]=http://192.168.1.170:9200
#spring.elasticsearch.rest.uris[1]=http://192.168.1.171:9200
3.实体类创建
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
@Document(indexName = "user", shards = 3, replicas = 2)
public class User {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Keyword, index = false)
private String password;
}
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
@Document
作用在类,标记实体类为文档对象,一般有四个属性
indexName:对应索引库名称
shards:分片数量,默认1
replicas:副本数量,默认1
@Id
作用在成员变量,标记一个字段作为id主键
@Field
作用在成员变量,标记为文档的字段,并指定字段映射属性:
type:字段类型,取值是枚举:FieldType
index:是否索引,布尔类型,默认是true
store:是否存储,布尔类型,默认是false
analyzer:分词器名称:ik_max_word
4.创建索引及映射
@SpringBootTest
class EsDemoApplicationTests {
// ElasticsearchTemplate是TransportClient客户端 已过期
// ElasticsearchRestTemplate是RestHighLevel客户端
@Autowired
ElasticsearchRestTemplate restTemplate;
@Test
void contextLoads() {
// 索引库操作对象
IndexOperations ops = this.restTemplate.indexOps(User.class);
ops.create(); // 初始化索引库
ops.putMapping(indexOps.createMapping()); // 声明创建映射
}
//新增文档
@Test
void save(){
restTemplate.save(new User(1L,"zhangsan",20,"zhangsan@126.com"));
}
//根据id查询文档
@Test
void get(){//根据id查找
User user = restTemplate.queryForObject(GetQuery.getById("1"), User.class);
System.out.println(user);
}
}
5.查询文档
//查询所有
@Test
void testMatchAll(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.matchAllQuery());
SearchHits<User> search = elasticsearchRestTemplate.search(builder.build(), User.class);
List<SearchHit<User>> searchHits = search.getSearchHits();
searchHits.forEach(s->{
User user = s.getContent();
String id = s.getId();
System.out.println(user);
System.out.println(id);
});
}
//匹配查询
@Test
void testMatch() {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.matchQuery("username", "cuicui"));
SearchHits<User> searchHits = elasticsearchRestTemplate.search(builder.build(), User.class);
List<SearchHit<User>> hitList = searchHits.getSearchHits();
hitList.forEach(hit->{
User user = hit.getContent();
System.out.println(user);
});
}
//布尔查询、过滤查询
@Test
void testBoolFilter(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("username","cuicui"))//布尔组合
.filter(QueryBuilders.rangeQuery("age").gte(22)));//过滤查询
SearchHits<User> searchHits = elasticsearchRestTemplate.search(builder.build(), User.class);
System.out.println(searchHits.getTotalHits());
List<SearchHit<User>> hits = searchHits.getSearchHits();
hits.forEach(h->{
System.out.println(h.getContent());
});
}
6.复杂查询 (匹配、过滤、分页、高亮、聚合度量查询 )
@Autowired
ElasticsearchRestTemplate restTemplate;
@Test
void testSearch(){
// 自定义搜索查询构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 放入搜索条件
queryBuilder.withQuery(QueryBuilders.matchQuery("name", "冰冰").operator(Operator.AND));
// 放入年龄的过滤条件
queryBuilder.withFilter(QueryBuilders.rangeQuery("age").gte(19).lte(23));
// 放入排序条件
queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC));
// 分页
queryBuilder.withPageable(PageRequest.of(0, 3));
// 高亮
queryBuilder.withHighlightBuilder(new HighlightBuilder().field("name").preTags("<em>").postTags("</em>"));
// 添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("pwdAgg").field("password"));
//打印设置的Filter的DSL语句
System.out.println(queryBuilder.build().getFilter().toString());
//打印设置的查询的DSL语句
System.out.println(queryBuilder.build().getQuery().toString());
// 执行查询操作
SearchHits<User> hits = this.restTemplate.search(queryBuilder.build(), User.class);
// 获取搜索结果集
List<SearchHit<User>> searchHits = hits.getSearchHits();
System.out.println("总共命中:" + hits.getTotalHits());
// 遍历打印
searchHits.forEach(searchHit -> {
System.out.println("===========================================");
User user = searchHit.getContent();
System.out.println("高亮前:" + user);
List<String> names = searchHit.getHighlightField("name");
user.setName(names.get(0));
System.out.println("高亮后:" + user);
});
// 解析聚合结果集
Aggregations aggregations = hits.getAggregations();
ParsedStringTerms pwdAgg = aggregations.get("pwdAgg");
pwdAgg.getBuckets().forEach(bucket -> {
System.out.println("--------------------------------------------");
System.out.println("桶的名称:" + bucket.getKeyAsString());
System.out.println("桶中的文档数量:" + bucket.getDocCount());
});
}
NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
Page<item>
:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
totalElements:总条数
totalPages:总页数
Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据
二、Repository文档操作
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
其中ElasticsearchRepository接口功能最强大。该接口的方法包括:
1.定义UserRepository:
public interface UserRepository extends ElasticsearchRepository<User, Long> {
}
2.测试类中新增
@Autowired
UserRepository userRepository;
@Test
void testAdd(){
this.userRepository.save(new User(1l, "zhang3", 20, "123456"));
}
修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。
执行成功之后查看数据:
3.删除
@Test void testDelete(){ this.userRepository.deleteById(1l); }
4.基本查询
5.查询一个:
@Test
void testFind(){
System.out.println(this.userRepository.findById(1l).get());
}
基本查询扩展:
三、RestHighLevelClient
@SpringBootTest
class EsDemoApplicationTests2 {
// springboot-elasticsearch 对应原生客户端提供了支持,直接注入即可使用
@Autowired
private RestHighLevelClient restHighLevelClient;
// jackson工具类
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
void testRestHighLevelClient() throws IOException {
// 搜索条件构建器
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 构建搜索请求体,设置索引库 并设置搜索条件构建器
SearchRequest searchRequest = new SearchRequest(new String[]{"user"}, sourceBuilder);
// 向构建器中放入搜索条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
sourceBuilder.query(boolQueryBuilder);
boolQueryBuilder.must(QueryBuilders.matchQuery("name", "冰冰").operator(Operator.AND));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("age").gte(19).lte(23));
// 排序
sourceBuilder.sort("age", SortOrder.DESC);
// 分页
sourceBuilder.from(2);
sourceBuilder.size(2);
// 高亮
sourceBuilder.highlighter(new HighlightBuilder().field("name").preTags("<em>").postTags("</em>"));
// 聚合
sourceBuilder.aggregation(AggregationBuilders.terms("pwdAgg").field("password"));
System.out.println(sourceBuilder);
// 执行搜索功能,返回响应对象
SearchResponse response = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response);
// 查询结果集
SearchHits hits = response.getHits();
System.out.println("一共命中:" + hits.getTotalHits());
SearchHit[] hitsHits = hits.getHits();
for (SearchHit hitsHit : hitsHits) {
String json = hitsHit.getSourceAsString();
User user = MAPPER.readValue(json, User.class);
System.out.println("高亮前:" + user);
// 高亮结果集
Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
user.setName(highlightField.fragments()[0].string());
System.out.println("高亮后:" + user);
}
// 聚合结果集
Aggregations aggregations = response.getAggregations();
ParsedStringTerms pwdAgg = aggregations.get("pwdAgg");
List<? extends Terms.Bucket> buckets = pwdAgg.getBuckets();
buckets.forEach(bucket -> {
System.out.println("桶的key:" + bucket.getKeyAsString());
System.out.println("桶中的元素数量:" + bucket.getDocCount());
});
}
}