07.ElasticSearch在springboot中使用
Posted 潮汐先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了07.ElasticSearch在springboot中使用相关的知识,希望对你有一定的参考价值。
ElasticSearch在springboot中使用
新建项目
新建一个springboot项目springboot_es
用于本次与ElasticSearch的整合,如下图
引入依赖
修改我们的pom.xml
,加入spring-boot-starter-data-elasticsearch
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
编写配置文件
由于ElasticSearch从7.x版本开始淡化TransportClient甚至于在8.x版本中遗弃,所以spring data elasticsearch推荐我们使用rest客户端RestHingLevelClient
(端口号使用9200)以及接口ElasticSearchRespositoy
。
- RestHighLevelClient 更强大,更灵活,但是不能友好的操作对象
- ElasticSearchRepository 对象操作友好
首先我们编写配置文件如下
/**
* ElasticSearch Rest Client config
* @author Christy
* @date 2021/4/29 19:40
**/
@Configuration
public class ElasticSearchRestClientConfig extends AbstractElasticsearchConfiguration{
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("192.168.8.101:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
springboot操作ES
RestHighLevelClient方式
有了上面的rest client,我们就可以在其他的地方注入该客户端对ElasticSearch进行操作。我们新建一个测试文件,使用客户端对ElasticSearch进行基本的操作
1.注入RestClient
/**
* ElasticSearch Rest client操作
*
* RestHighLevelClient 更强大,更灵活,但是不能友好的操作对象
* ElasticSearchRepository 对象操作友好
*
* 我们使用rest client 主要测试文档的操作
* @Author Christy
* @Date 2021/4/29 19:51
**/
@SpringBootTest
public class TestRestClient {
// 复杂查询使用:比如高亮查询
@Autowired
private RestHighLevelClient restHighLevelClient;
}
2.插入一条文档
/**
* 新增一条文档
* @author Christy
* @date 2021/4/29 20:17
*/
@Test
public void testAdd() throws IOException {
/**
* 向ES中的索引christy下的type类型中添加一天文档
*/
IndexRequest indexRequest = new IndexRequest("christy","user","11");
indexRequest.source("{\\"name\\":\\"齐天大圣孙悟空\\",\\"age\\":685,\\"bir\\":\\"1685-01-01\\",\\"introduce\\":\\"花果山水帘洞美猴王齐天大圣孙悟空是也!\\"," +
"\\"address\\":\\"花果山\\"}", XContentType.JSON);
IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse.status());
}
我们可以看到文档插入成功,我们去kibana中查询该条文档
完全没有问题的。
3.删除一条文档
/**
* 删除一条文档
* @author Christy
* @date 2021/4/29 20:18
*/
@Test
public void deleteDoc() throws IOException {
// 我们把特朗普删除了
DeleteRequest deleteRequest = new DeleteRequest("christy","user","rYBNG3kBRz-Sn-2f3ViU");
DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
}
4.更新一条文档
/**
* 更新一条文档
* @author Christy
* @date 2021/4/29 20:19
*/
@Test
public void updateDoc() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("christy","user","p4AtG3kBRz-Sn-2fMFjj");
updateRequest.doc("{\\"name\\":\\"调皮捣蛋的hardy\\"}",XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
5.批量更新文档
/**
* 批量更新
* @author Christy
* @date 2021/4/29 20:42
*/
@Test
public void bulkUpdate() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
// 添加
IndexRequest indexRequest = new IndexRequest("christy","user","13");
indexRequest.source("{\\"name\\":\\"天蓬元帅猪八戒\\",\\"age\\":985,\\"bir\\":\\"1685-01-01\\",\\"introduce\\":\\"天蓬元帅猪八戒因调戏嫦娥被贬下凡\\",\\"address\\":\\"高老庄\\"}", XContentType.JSON);
bulkRequest.add(indexRequest);
// 删除
DeleteRequest deleteRequest01 = new DeleteRequest("christy","user","pYAtG3kBRz-Sn-2fMFjj");
DeleteRequest deleteRequest02 = new DeleteRequest("christy","user","uhTyGHkBExaVQsl4F9Lj");
DeleteRequest deleteRequest03 = new DeleteRequest("christy","user","C8zCGHkB5KgTrUTeLyE_");
bulkRequest.add(deleteRequest01);
bulkRequest.add(deleteRequest02);
bulkRequest.add(deleteRequest03);
// 修改
UpdateRequest updateRequest = new UpdateRequest("christy","user","10");
updateRequest.doc("{\\"name\\":\\"炼石补天的女娲\\"}",XContentType.JSON);
bulkRequest.add(updateRequest);
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
BulkItemResponse[] items = bulkResponse.getItems();
for (BulkItemResponse item : items) {
System.out.println(item.status());
}
}
在kibana中查询结果
6.查询文档
@Test
public void testSearch() throws IOException {
//创建搜索对象
SearchRequest searchRequest = new SearchRequest("christy");
//搜索构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery())//执行查询条件
.from(0)//起始条数
.size(10)//每页展示记录
.postFilter(QueryBuilders.matchAllQuery()) //过滤条件
.sort("age", SortOrder.DESC);//排序
//创建搜索请求
searchRequest.types("user").source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("符合条件的文档总数: "+searchResponse.getHits().getTotalHits());
System.out.println("符合条件的文档最大得分: "+searchResponse.getHits().getMaxScore());
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
}
ElasticSearchRepository方式
1.准备工作
ElasticSearchRepository方式主要通过注解和对接口实现的方式来实现ES的操作,我们在实体类上通过注解配置ES索引的映射关系后,当实现了ElasticSearchRepository接口的类第一次操作ES进行插入文档的时候,ES会自动生成所需要的一切。但是该种方式无法实现高亮查询,想要实现高亮查询只能使用RestHighLevelClient
开始之前我们需要熟悉一下接口方式为我们提供的注解,以及编写一些基础的类
1.清空ES数据
2.了解注解
@Document
: 代表一个文档记录
indexName
: 用来指定索引名称
type
: 用来指定索引类型
@Id
: 用来将对象中id和ES中_id映射
@Field
: 用来指定ES中的字段对应Mapping
type
: 用来指定ES中存储类型
analyzer
: 用来指定使用哪种分词器
3.新建实体类
/**
* 用在类上作用:将Emp的对象映射成ES中一条json格式文档
* indexName: 用来指定这个对象的转为json文档存入那个索引中 要求:ES服务器中之前不能存在此索引名
* type : 用来指定在当前这个索引下创建的类型名称
*
* @Author Christy
* @Date 2021/4/29 21:22
*/
@Data
@Document(indexName = "christy",type = "user")
public class User {
@Id //用来将对象中id属性与文档中_id 一一对应
private String id;
// 用在属性上 代表mapping中一个属性 一个字段 type:属性 用来指定字段类型 analyzer:指定分词器
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Date)
@JsonFormat(pattern = "yyyy-MM-dd")
private Date bir;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String content;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String address;
}
4.UserRepository
/**
* @Author Christy
* @Date 2021/4/29 21:23
**/
public interface
extends ElasticsearchRepository<User,String> {
}
5.TestUserRepository
/**
* @Author Christy
* @Date 2021/4/29 21:51
**/
@SpringBootTest
public class TestUserRepository {
@Autowired
private UserRepository userRepository;
}
2.保存文档
@Test
public void testSaveAndUpdate(){
User user = new User();
// id初识为空,此操作为新增
user.setId(UUID.randomUUID().toString());
user.setName("唐三藏");
user.setBir(new Date());
user.setIntroduce("西方世界如来佛祖大弟子金蝉子转世,十世修行的好人,得道高僧!");
user.setAddress("大唐白马寺");
userRepository.save(user);
}
3.修改文档
@Test
public void testSaveAndUpdate(){
User user = new User();
// 根据id修改信息
user.setId("1666eb47-0bbf-468b-ab45-07758c741461");
user.setName("唐三藏");
user.setBir(new Date());
user.setIntroduce("俗家姓陈,状元之后。西方世界如来佛祖大弟子金蝉子转世,十世修行的好人,得道高僧!");
user.setAddress("大唐白马寺");
userRepository.save(user);
}
4.删除文档
repository接口默认提供了4种删除方式,我们演示根据id进行删除
@Test
public void deleteDoc(){
userRepository.deleteById("1666eb47-0bbf-468b-ab45-07758c741461");
}
5.检索一条记录
@Test
public void testFindOne(){
Optional<User> optional = userRepository.findById("1666eb47-0bbf-468b-ab45-07758c741461");
System.out.println(optional.get());
}
6.查询所有
@Test
public void testFindAll(){
Iterable<User> all = userRepository.findAll();
all.forEach(user-> System.out.println(user));
}
7.排序
@Test
public void testFindAllSort(){
Iterable<User> all = userRepository.findAll(Sort.by(Sort.Order.asc("age")));
all.forEach(user-> System.out.println(user));
}
8.分页
@Test
public void testFindPage(){
//PageRequest.of 参数1: 当前页-1
Page<User> search = userRepository.search(QueryBuilders.matchAllQuery(), PageRequest.of(1, 1));
search.forEach(user-> System.out.println(user));
}
9.自定义查询
先给大家看一个表,是不是很晕_(¦3」∠)_
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or | findByNameOrPrice | {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is | findByName | {"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not | findByNameNot | {"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between | findByPriceBetween | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual | findByPriceLessThan | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual | findByPriceGreaterThan | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before | findByPriceBefore | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After | findByPriceAfter | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like | findByNameLike | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith | findByNameStartingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith | findByNameEndingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing | findByNameContaining | {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In | findByNameIn (Collection<String>names) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn (Collection<String>names) | {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | {"bool" : {"must" : {"field" : {"available" : true}}}} |
False | findByAvailableFalse | {"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy | findByAvailable TrueOrderByNameDesc | {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
这个表格看起来复杂,实际上也不简单,但是确实很牛逼。我们只要按照上面的定义在接口中定义相应的方法,无须写实现就可实现我们想要的功能
举个例子,上面有个findByName是下面这样定义的
假如我们现在有个需求需要按照名字查询用户,我们可以在UserRepository
中定义一个方法,如下
// 根据姓名查询
List<User> findByName(String name);
系统提供的查询方法中findBy
是一个固定写法,像上面我们定义的方法findByName
,其中Name是我们实体类中的属性名,这个必须对应上。也就是说这个findByName不仅仅局限于name
,还可以findByAddress、findByAge等等;
现在就拿findByName来讲,我们要查询名字叫唐三藏的用户
@Test
public void testFindByName(){
List<User> userList = userRepository.findByName("唐三藏");
userList.forEach(user-> System.out.println(user));
}
其实就是框架底层直接使用下面的命令帮我们实现的查询
GET /christy/user/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name":"?"
}
}
]
}
}
}
10.高亮查询
我们上面说了,ElasticSearchRepository实现不了高亮查询,想要实现高亮查询还是需要使用RestHighLevelClient方式。最后我们使用rest clientl实现一次高亮查询
@Test
public void testHighLightQuery() throws IOException, ParseException {
// 创建搜索请求
SearchRequest searchRequest = new SearchRequest("christy");
// 创建搜索对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("introduce", "唐僧")) // 设置查询条件
.from(0) // 起始条数(当前页-1)*size的值
.size(10) // 每页展示条数
.sort("age", SortOrder.DESC) // 排序
.highlighter(new HighlightBuilder().field("*").requireFieldMatch(false).preTags("<span style='color:red;'>").postTags((转)Spring Boot