1、建项目:SpringData_Elasticsearch_5.6.8
2、改pom
注意,本次使用的ElasticSearch的版本为5.6.8,对应的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.1.16.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>es5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es5</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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、建yml
# 注意springboot的版本
# elasticsearch的版本为5.6.8
spring:
data:
elasticsearch:
cluster-name: cluster_es
cluster-nodes: 192.168.77.138:9300 # elasticsearch的连接
4、启动类
package com.example.es5;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Es5Application {
public static void main(String[] args) {
SpringApplication.run(Es5Application.class, args);
}
}
5、实体类
package com.example.es5.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* @Author: xj0927
* @Description:
* @Date Created in 2021-01-05 10:52
* @Modified By:
*/
@Document(indexName = "ems", type = "emp") //索引_类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
@Id
@Field(type = FieldType.Integer, store = true)
private Integer id;
@Field(type = FieldType.Text, store = true, analyzer = "ik_max_word") //分词类型
private String title;
@Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
private String content;
}
6、接口
ElasticSearch 提供了内置基本crud的接口 ElasticSearchRepository,我们只需要继承它即可
package com.example.es5.repository;
import com.example.es5.entity.Article;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/**
* @Author: xj0927
* @Description: Spring Data 提供的crud的操作对象为ElasticSearchTemplate
* 参数1:实体类
* 参数2:id类型
* @Date Created in 2021-01-05 10:59
* @Modified By:
*/
public interface ArticleRepository extends ElasticsearchRepository<Article,Integer> {
List<Article> findByTitle(String title);
List<Article> findByTitleAndContent(String title, String content);
//分页查询
List<Article> findByTitleOrContent(String title, String content, Pageable pageable);
}
通过 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}}}} |
7、测试
package com.example.es5.repository;
import com.example.es5.entity.Article;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import java.util.Optional;
/**
* @Author: xj0927
* @Description:
* @Date Created in 2021-01-05 11:07
* @Modified By:
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ArticleRepositoryTest {
@Autowired
private ElasticsearchTemplate template;//es的复杂操作
@Autowired
private ArticleRepository repository; //内置对文档的crud
@Test
public void test1() {
//es不存在索引的情况下
boolean result = template.createIndex(Article.class);
System.out.println(result);
//存在索引的情况下
template.putMapping(Article.class);
}
@Test
public void test2() {
//添加/更新文档[id存在则更新,不存在则添加]
Article article = new Article();
article.setId(1);
article.setTitle("女护士路遇昏迷男子跪地抢救:救人是职责更是本能1");
article.setContent("这是一个美丽的女护士妹妹");
repository.save(article);
//删除文档
repository.deleteById(1);
repository.deleteAll();
//查询
//根据id
Optional<Article> optional = repository.findById(1);
Article article1 = optional.get();
System.out.println(article1);
//查询所有
Iterable<Article> iterable = repository.findAll();
iterable.forEach(e -> {
System.out.println(e);
});
//扩展查询
List<Article> list = repository.findByTitle("女护士");
System.out.println(list);
//分页查询
List<Article> list2 = repository.findByTitleOrContent("护士", "妹妹", PageRequest.of(0, 2));
System.out.println(list2);
}
@Test
public void test3() {
//本地查询[复杂查询时使用:使用官方提供的api]
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery("女护士").defaultField("title"))//查询
.withPageable(PageRequest.of(0, 2))//分页
.build();
List<Article> list = template.queryForList(query, Article.class);
list.forEach(e -> {
System.out.println(e);
});
}
}
8、聚合查询
实体类
@Document(indexName = "car_index", type = "car_type")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
@Id
@Field(type = FieldType.Long, store = true)
private Long id;
@Field(type = FieldType.Text, store = true, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Text, store = true, analyzer = "ik_max_word", fielddata = true)
private String color;
@Field(type = FieldType.Text, store = true, analyzer = "ik_max_word", fielddata = true)
private String brand;
@Field(type = FieldType.Double, store = true)
private Double price;
}
接口
public interface CarRepository extends ElasticsearchRepository<Car, Long> {
}
测试
先添加一些数据
@RunWith(SpringRunner.class)
@SpringBootTest
public class CarRepositoryTest {
@Autowired
private CarRepository carDao; //内置对文档的crud
@Test
public void initIndex() {
carDao.save(new Car(1l, "比亚迪A1", "红色", "比亚迪", 50000d));
carDao.save(new Car(2L, "比亚迪A2", "白色", "比亚迪", 70000d));
carDao.save(new Car(3l, "比亚迪A3", "白色", "比亚迪", 80000d));
carDao.save(new Car(4l, "比亚迪A4", "红色", "比亚迪", 60000d));
carDao.save(new Car(5l, "比亚迪A5", "红色", "比亚迪", 90000d));
carDao.save(new Car(6l, "宝马A1", "红色", "宝马", 10000d));
carDao.save(new Car(7l, "宝马A2", "黑色", "宝马", 20000d));
carDao.save(new Car(8l, "宝马A3", "黑色", "宝马", 30000d));
carDao.save(new Car(9l, "宝马A4", "红色", "宝马", 40000d));
carDao.save(new Car(10l, "宝马A5", "红色", "宝马", 50000d));
carDao.save(new Car(11l, "奔驰A1", "红色", "奔驰", 10000d));
carDao.save(new Car(12l, "奔驰A2", "黑色", "奔驰", 20000d));
carDao.save(new Car(13l, "奔驰A3", "黑色", "奔驰", 30000d));
carDao.save(new Car(14l, "奔驰A4", "红色", "奔驰", 40000d));
carDao.save(new Car(15l, "奔驰A5", "红色", "奔驰", 50000d));
}
}
聚合查询:[仅划分桶]
聚合查询:[划分桶+桶内度量]
代码实现:
仅划分桶:
@Test
public void test() {
//构建查询条件
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withSourceFilter(new FetchSourceFilter(new String[]{}, new String[]{"brand"}))//排除字段
.addAggregation(AggregationBuilders.terms("group_by_color").field("color"))//====>聚合查询[划分桶]
.build();
//执行查询p[将查询结果转化为聚合page]
AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(query);
//获取结果[获取对应聚合结果]
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
buckets.forEach(e -> {
System.out.println("聚合条件:" + e.getKeyAsString());
System.out.println("聚合结果:" + e.getDocCount());
System.out.println("*********************");
});
}
划分桶+内部聚合:
@Test
public void test1() {
//构建查询条件
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withSourceFilter(new FetchSourceFilter(new String[]{}, new String[]{"brand"}))//排除字段
.addAggregation(AggregationBuilders.terms("group_by_color").field("color").subAggregation(AggregationBuilders.avg("avg_price").field("price")))//====>聚合查询[划分桶+桶度量]
.build();
//执行查询p[将查询结果转化为聚合page]
AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(query);
//获取结果[获取对应聚合结果]
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
buckets.forEach(e -> {
//桶
System.out.println("聚合条件:" + e.getKeyAsString());
System.out.println("聚合结果:" + e.getDocCount());
//取得内部聚合
InternalAvg avg = (InternalAvg) e.getAggregations().asMap().get("avg_price");
System.out.println("内部聚合条件:" + avg.getName());
System.out.println("内部聚合结果:" + avg.value());
System.out.println("*********************");
});
}