14. SpringBoot Data操作ES
14.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
14.2 编写yml配置
- spring-data(2~3.x版本配置)
spring:
data:
elasticsearch:
cluster-nodes: 172.16.251.142:9300
- spring-data(新版本推荐配置) RestHighLevelClient rest客户端 ElasticSearchRespositoy接口
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("192.168.202.200:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
14.3 编写entity
@Document(indexName = "dangdang",type = "book")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
@Id
private String id;
@Field(type = FieldType.Text,analyzer ="ik_max_word")
private String name;
@Field(type = FieldType.Date)
@JsonFormat(pattern="yyyy-MM-dd")
private Date createDate;
@Field(type = FieldType.Keyword)
private String author;
@Field(type = FieldType.Text,analyzer ="ik_max_word")
private String content;
}
@Document
: 代表一个文档记录
indexName
: 用来指定索引名称
type
: 用来指定索引类型
@Id
: 用来将对象中id和ES中_id映射
@Field
: 用来指定ES中的字段对应Mapping
type
: 用来指定ES中存储类型
analyzer
: 用来指定使用哪种分词器
14.4 编写BookRepository
public interface BookRepository extends ElasticsearchRepository<Book,String> {
}
14.5 索引or更新一条记录
NOTE:这种方式根据实体类中中配置自动在ES创建索引,类型以及映射
@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class TestSpringBootDataEs {
@Autowired
private BookRepository bookRespistory;
/**
* 添加索引和更新索引 id 存在更新 不存在添加
*/
@Test
public void testSaveOrUpdate(){
Book book = new Book();
book.setId("21");
book.setName("小陈");
book.setCreateDate(new Date());
book.setAuthor("李白");
book.setContent("这是中国的好人,这真的是一个很好的人,李白很狂");
bookRespistory.save(book);
}
}
14.6 删除一条记录
/**
* 删除一条索引
*/
@Test
public void testDelete(){
Book book = new Book();
book.setId("21");
bookRespistory.delete(book);
}
14.7 查询
/**
* 查询所有
*/
@Test
public void testFindAll(){
Iterable<Book> books = bookRespistory.findAll();
for (Book book : books) {
System.out.println(book);
}
}
/**
* 查询一个
*/
@Test
public void testFindOne(){
Optional<Book> byId = bookRespistory.findById("21");
System.out.println(byId.get());
}
14.8 查询排序
/**
* 排序查询
*/
@Test
public void testFindAllOrder(){
Iterable<Book> books = bookRespistory.findAll(Sort.by(Sort.Order.asc("createDate")));
books.forEach(book -> System.out.println(book) );
}
14.9 自定义基本查询
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}}}} |
public interface BookRepository extends ElasticsearchRepository<Book,String> {
//根据作者查询
List<Book> findByAuthor(String keyword);
//根据内容查询
List<Book> findByContent(String keyword);
//根据内容和名字查
List<Book> findByNameAndContent(String name,String content);
//根据内容或名称查询
List<Book> findByNameOrContent(String name,String content);
//范围查询
List<Book> findByPriceBetween(Double start,Double end);
//查询名字以xx开始的
List<Book> findByNameStartingWith(String name);
//查询某个字段值是否为false
List<Book> findByNameFalse();
//.......
}
14.10 实现复杂查询
分页查询并排序
@Test
public void testSearchPage() throws IOException {
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from(0).size(2).sort("age", SortOrder.DESC).query(QueryBuilders.matchAllQuery());
searchRequest.indices("ems").types("emp").source(sourceBuilder);
SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = search.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
高亮查询
@Test
public void testSearchHig() throws IOException {
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("content").requireFieldMatch(false).preTags("<span style='color:red;'>").postTags("</span>");
sourceBuilder.from(0).size(2).sort("age", SortOrder.DESC).highlighter(highlightBuilder).query(QueryBuilders.termQuery("content","框架"));
searchRequest.indices("ems").types("emp").source(sourceBuilder);
SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = search.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
highlightFields.forEach((k,v)-> System.out.println("key: "+k + " value: "+v.fragments()[0]));
}
}
下面是编写的代码:
1、自动创建springboot项目
2、maven引入springboot-data的jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
3、编写entity:
package com.nono.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
@Data
/**
* 用在类上 作用:将Emp的对象映射成ES中一条json格式文档
* indexName: 用来指定这个对象的转为json文档存入那个索引中 要求:ES服务器中之前不能存在此索引名
* type : 用来指定在当前这个索引下创建的类型名称
*/
@Document(indexName = "ems",type = "emp")
public class Emp {
@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;
}
springboot操作es有两种情况 第一种是用RestHighLevelClient,第二种是用ElasticSearchRepository
4、编写ElasticSearchRestClientConfig继承AbstractElasticsearchConfiguration
package com.nono.config;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
@Configuration
public class ElasticSearchRestClientConfig extends AbstractElasticsearchConfiguration {
//这个client 用来替换 transportClient(9300)对象
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
//定义客户端配置对象 RestClient 9200
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("192.168.159.5:9200")
.build();
//通过RestClients对象创建
return RestClients.create(clientConfiguration).rest();
}
}
5、编写TestRestClient注入RestHighLevelClient的方式进行操作es。
(注意我们用自动构建的springboot项目在测试的时候只要用@SpringBootTest就可以了。不过记住@Test要引入的是import org.junit.jupiter.api.Test;)
package com.nono;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
/**
* client 操作
*
* RestHighLevelClient 强大 更灵活 不能友好的对象操作
*
* ElasticSearchRepository 对象操作友好
*
*/
@SpringBootTest
public class TestRestClient {
@Autowired
private RestHighLevelClient restHighLevelClient;//复杂查询使用 高亮查询 指定条件查询 如同transportClient
//添加文档
@Test
public void testAddIndex() throws IOException {
IndexRequest indexRequest = new IndexRequest("ems","emp","14");
indexRequest.source("{\"name\":\"小黑\",\"age\":23}", XContentType.JSON);
IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse.status());
}
//删除文档
@Test
public void testDeleteIndex() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest("ems","emp","14");
DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
//更新文档
@Test
public void testUpdateIndex() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("ems","emp","12");
updateRequest.doc("{\"name\":\"小黑\",\"age\":30}",XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
//批量更新
@Test
public void testBulk() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
//添加
IndexRequest request = new IndexRequest("ems","emp","13");
request.source("{\"name\":\"李四\",\"age\":23}",XContentType.JSON);
//删除
//修改
bulkRequest.add(request);
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
BulkItemResponse[] items = bulkResponse.getItems();
for (BulkItemResponse item : items) {
System.out.println(item.status());
}
}
//查询所有
@Test
public void testQueryAll() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
//创建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//查询所有
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.types("emp").source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
@Test
public void testSearch() throws IOException {
//创建搜索对象
SearchRequest searchRequest = new SearchRequest("ems");
//搜索构建对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery())//执行查询条件
.from(0)//起始条数
.size(20)//每页展示记录
.postFilter(QueryBuilders.matchAllQuery()) //过滤条件
.sort("age", SortOrder.DESC)//排序
.highlighter(new HighlightBuilder().field("*").requireFieldMatch(false));//高亮
//创建搜索请求
searchRequest.types("emp").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());
}
}
}
6、编写EmpRepository接口 实现ElasticsearchRepository
package com.nono.elasticsearch.dao;
import com.nono.entity.Emp;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
//自定义EmpRepository
public interface EmpRepository extends ElasticsearchRepository<Emp,String> {
//根据姓名查询
List<Emp> findByName(String name);
//根据年龄查询
List<Emp> findByAge(Integer age);
//根据姓名和地址
List<Emp> findByNameAndAddressAndAge(String name,String address,Integer age);
//根据姓名或年龄查询
List<Emp> findByNameOrAge(String name,Integer age);
//查询年龄大于等于 8
List<Emp> findByAgeGreaterThanEqual(Integer value);
}
7、编写TestEmpRespository测试
package com.nono;
import com.nono.elasticsearch.dao.EmpRepository;
import com.nono.entity.Emp;
import lombok.ToString;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import java.io.IOException;
import java.sql.SQLOutput;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
@SpringBootTest
public class TestEmpRespository {
@Autowired
private EmpRepository empRepository; //1
@Autowired
private RestHighLevelClient restHighLevelClient;//2
//保存|更新一条文档 id存在更新 id不存在 添加
@Test
public void testSave(){
Emp s = new Emp();
//s.setId("a1389021-2ecf-40cb-95e7-4c3be73604d9");
s.setAge(2);
s.setBir(new Date());
s.setName("张君宝一代武侠");
s.setAddress("武当山武侠学院毕业");
s.setContent("武侠大师!");
empRepository.save(s);
}
//根据id删除一条文档
@Test
public void testDelete(){
empRepository.deleteById("a1389021-2ecf-40cb-95e7-4c3be73604d9");
}
//删除所有
@Test
public void testDeleteAll(){
empRepository.deleteAll();
}
//检索一条记录
@Test
public void testFindOne(){
Optional<Emp> optional = empRepository.findById("a1389021-2ecf-40cb-95e7-4c3be73604d9");
System.out.println(optional.get());
}
//查询所有 排序
@Test
public void testFindAll(){
Iterable<Emp> all = empRepository.findAll(Sort.by(Sort.Order.asc("age")));
all.forEach(emp-> System.out.println(emp));
}
//分页
@Test
public void testFindPage(){
//PageRequest.of 参数1: 当前页-1
Page<Emp> search = empRepository.search(QueryBuilders.matchAllQuery(), PageRequest.of(1, 1));
search.forEach(emp-> System.out.println(emp));
}
//基础查询 根据姓名 姓名和年龄 地址 等
@Test
public void testFindByName(){
List<Emp> emps = empRepository.findByName("张君宝");
List<Emp> emps1 = empRepository.findByAge(23);
List<Emp> emps2 = empRepository.findByNameAndAddressAndAge("张君宝", "武当山",23);
List<Emp> emps3 = empRepository.findByNameOrAge("张君宝", 2);
List<Emp> emps4 = empRepository.findByAgeGreaterThanEqual(23);
emps4.forEach(emp-> System.out.println(emp));
}
//复杂查询RestHighLevelClient 高亮
@Test
public void testSearchQuery() throws IOException, ParseException {
List<Emp> emps = new ArrayList<Emp>();
//创建搜索请求
SearchRequest searchRequest = new SearchRequest("ems");
//创建搜索对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("content","武侠"))//设置条件
.sort("age", SortOrder.DESC)//排序
.from(0) //起始条数(当前页-1)*size的值
.size(20)//每页展示条数
.highlighter(new HighlightBuilder().field("*").requireFieldMatch(false).preTags("<span style='color:red'>").postTags("</span>"));//设置高亮
searchRequest.types("emp").source(searchSourceBuilder);
//执行搜索
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
//原始文档
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
Emp emp = new Emp();
emp.setId(hit.getId());
emp.setAge(Integer.parseInt(sourceAsMap.get("age").toString()));
emp.setBir(new SimpleDateFormat("yyyy-MM-dd").parse(sourceAsMap.get("bir").toString()));
emp.setContent(sourceAsMap.get("content").toString());
emp.setName(sourceAsMap.get("name").toString());
emp.setAddress(sourceAsMap.get("address").toString());
//高亮字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if(highlightFields.containsKey("content")){
emp.setContent(highlightFields.get("content").fragments()[0].toString());
}
if(highlightFields.containsKey("name")){
emp.setName(highlightFields.get("name").fragments()[0].toString());
}
if(highlightFields.containsKey("address")){
emp.setAddress(highlightFields.get("address").fragments()[0].toString());
}
//放入集合
emps.add(emp);
}
emps.forEach(emp-> System.out.println(emp));
}
}