1、建SpringBoot项目:elaticsearch_springboot
2、改pom
注意如果你使用自动引入依赖,默认使用的springboot版本为最新的,需要将版本改低一点!!!
<?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.2.5.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>elaticsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elasticsearch</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
</dependency>
<!--elasticsearch for springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3、配置类
两种方式:推荐第二种
方式一:spring-data(2~3.x版本配置)
spring:
data:
elasticsearch:
cluster-nodes: 192.168.77.138:9300
方式二: spring-data(新版本推荐配置) RestHighLevelClient rest客户端 ElasticSearchRespositoy接口
/**
* @Author: xj0927
* @Description: RestHighLevelClient 客户端配置
* @Date Created in 2020-12-30 14:05
* @Modified By:
*/
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("192.168.77.138:9200") //===>与kibana客户端类型都是restful分格,都是连接9200端口
.build();
return RestClients.create(clientConfiguration).rest();
}
}
4、实体类
ES操作相关的实体类,可以只需要业务实体类的某一些字段
@Document: 代表一个文档记录
-
indexName: 用来指定索引名称
-
type: 用来指定索引类型
@Id: 用来将对象中id和ES中_id映射
@Field: 用来指定ES中的字段对应Mapping
-
type: 用来指定ES中存储类型
-
analyzer: 用来指定使用哪种分词器
/**
* [用在类上]作用: 将Emp的对象映射成ES中一条json格式文档
* indexName : 用来指定这个对象的转为json文档存入那个索引中 要求:ES服务器中之前不能存在此索引名
* type : 用来指定在当前这个索引下创建的类型名称
*/
@Document(indexName = "ems", type = "emp")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
@Id //与ES中_id进行映射
private String id;
/**
* @Description: 用在属性上 代表mapping中一个属性 一个字段
* type:属性 用来指定字段类型 analyzer:指定分词器
* @Author: xj0927
* @Date Created in 2020/12/30 14:38
*/
@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.Keyword)
private String address;
}
5、通用接口
对一些简单的crud操作,可以使用 ElasticSearchRespositoy 接口
/**
* @Author: xj0927
* @Description: ElasticsearchRepository :ES提供的操作CRUD接口
* 第一个参数:指定对象类型
* 第二个参数:ID类型
* @Date Created in 2020-12-30 14:23
* @Modified By:
*/
public interface EmpRepository extends ElasticsearchRepository<Emp,String> {
}
6、ElasticSearchRespositoy 实现基本crud
通过最定义接口继承ES提供的ElasticsearchRepository接口来实现
索引or更新一条记录
这种方式根据实体类中中配置自动在ES创建索引,类型以及映射
- 不传入id表示添加操作,会自动生成id
- 传入id,ES中有此id表示更新,没有表示添加
@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);
}
}
删除一条记录
两种方式:根据id,根据对象属性值
@Test
public void testDelete(){
Book book = new Book();
book.setId("21");
bookRespistory.delete(book);
}
查询
/**
* 查询一个
*/
@Test
public void testFindOne(){
Optional<Book> byId = bookRespistory.findById("21");
System.out.println(byId.get());
}
/**
* 查询所有
*/
@Test
public void testFindAll(){
Iterable<Book> books = bookRespistory.findAll();
for (Book book : books) {
System.out.println(book);
}
}
查询并排序
@Test
public void testFindAllOrder(){
Iterable<Book> books = bookRespistory.findAll(Sort.by(Sort.Order.asc("createDate")));
books.forEach(book -> System.out.println(book) );
}
自定义基本查询
通过 ElasticsearchRepository 接口除了ES提供的api还可以在自定义接口中自定义一些查询的方法
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 :<= | findByPriceLessThanEqual | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual :>= | findByPriceGreaterThanEqual | {"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 (Collectionnames) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn (Collectionnames) | {"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();
//.......
//根据名字查询,然后 根据Pageable 参数进行分页
//调用时:PageRequest page = PageRequest.of(0, 2);
List<Emp> findByContent(String name, Pageable pageable);
}
7、RestHighLevelClient 实现复杂查询
分页查询并排序
@Test
public void testSearchPage() throws IOException {
//查询请求
SearchRequest searchRequest = new SearchRequest();
//查询条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from(0).size(2).sort("age", SortOrder.ASC).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 testLight() throws IOException {
//集合存放查找到的数据
List<Emp> list = new ArrayList<>();
//查询请求
SearchRequest searchRequest = new SearchRequest();
//查询条件[对象]
SearchSourceBuilder builder = new SearchSourceBuilder();
//高亮配置
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("*").requireFieldMatch(false).preTags("<span style='color:red'>").postTags("</span>");
//具体按...查询
builder.from(0).size(2)
.sort("age", SortOrder.DESC)
.highlighter(highlightBuilder)
.query(QueryBuilders.multiMatchQuery("小黑喜欢小红", "name", "content"));
//从哪个索引/类型查找
searchRequest.indices("ems").types("emp").source(builder);
//===>查询方法
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("符合条件总数:" + searchResponse.getHits().getTotalHits());
System.out.println("最大得分:" + searchResponse.getHits().getMaxScore());
System.out.println("每条文档详细信息===>");
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit s :hits) {
//===>原文档部分
System.out.println(s.getSourceAsMap());
//返回对象
Emp emp = new Emp();
emp.setId((String) s.getSourceAsMap().get("id"));
emp.setName((String) s.getSourceAsMap().get("name"));
emp.setContent((String) s.getSourceAsMap().get("content"));
emp.setAddress((String) s.getSourceAsMap().get("address"));
emp.setAge((Integer) s.getSourceAsMap().get("age"));
//==>高亮部分
Map<String, HighlightField> highlightFields = s.getHighlightFields();
if(highlightFields.containsKey("name")){
emp.setName(highlightFields.get("name").fragments()[0].toString());
}
if(highlightFields.containsKey("content")){
emp.setContent(highlightFields.get("content").fragments()[0].toString());
}
list.add(emp);
}
//===>存入对象的文档[包括高亮部分]
System.out.println("===>存入对象的文档[包括高亮部分]");
list.forEach(emp -> {
System.out.println(emp);
});
}
为什么要分开拿原文档和高亮部分: