前面在 ubuntu 完成安裝 elasticsearch,現在我們SpringBoot將集成elasticsearch。
1、創建SpringBoot項目
我們這邊直接引入NoSql中Spring Data Elasticsearch啟動器。
創建項目完成后。
項目結構:
pom文件:(新增 lombok 簡化pojo)
<?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 http://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.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.yatces.elasticsearch</groupId> <artifactId>elasticsearch-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>elasticsearch-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.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2、添加 elasticsearch 配置
本人習慣 yml 文件,將 application.properties 重命名為 application.yml
spring: data: elasticsearch: cluster-name: elasticsearch cluster-nodes: 192.168.78.130:9300
3、新增實體類
@Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "product",type = "item",shards = 1,replicas = 0) public class Item { @Id Long id; @Field(type = FieldType.Text,analyzer = "ik_max_word") String title; //標題 @Field(type = FieldType.Keyword) String category;// 分類 @Field(type = FieldType.Keyword) String brand; // 品牌 @Field(type = FieldType.Double) Double price; // 價格 @Field(index = false, type = FieldType.Keyword) String images; // 圖片地址 }
主要注解:
@Document 作用在類,標記實體類為文檔對象,一般有四個屬性
indexName:對應索引庫名稱
type:對應在索引庫中的類型
shards:分片數量,默認5
replicas:副本數量,默認1
@Id 作用在成員變量,標記一個字段作為id主鍵
@Field 作用在成員變量,標記為文檔的字段,並指定字段映射屬性:
type:字段類型,取值是枚舉:FieldType
index:是否索引,布爾類型,默認是true
store:是否存儲,布爾類型,默認是false
analyzer:分詞器名稱:ik_max_word
4、編寫測試
4.1新建ItemTest
用於測試 elasticsearch 的使用,使用 ElasticsearchTemplate 操作索引。
@RunWith(SpringRunner.class) @SpringBootTest(classes = ElasticsearchDemoApplication.class) public class ItemTest { @Autowired private ElasticsearchTemplate elasticsearchTemplate; }
4.2創建索引和映射
@Test public void testCreate(){ // 創建索引,會根據Item類的@Document注解信息來創建 elasticsearchTemplate.createIndex(Item.class); // 配置映射,會根據Item類中的id、Field等字段來自動完成映射 elasticsearchTemplate.putMapping(Item.class); }
在 Kibana通過 GET product/_mapping 查詢結果
{ "product": { "mappings": { "item": { "properties": { "brand": { "type": "keyword" }, "category": { "type": "keyword" }, "images": { "type": "keyword", "index": false }, "price": { "type": "double" }, "title": { "type": "text", "analyzer": "ik_max_word" } } } } } }
4.3刪除索引
@Test
public void testDelete(){
//elasticsearchTemplate.deleteIndex(Item.class);
// indexName = "product"
elasticsearchTemplate.deleteIndex("product");
}
Kibana 再次查詢,報404。
{ "error": { "root_cause": [ { "type": "index_not_found_exception", "reason": "no such index", "resource.type": "index_or_alias", "resource.id": "product", "index_uuid": "_na_", "index": "product" } ], "type": "index_not_found_exception", "reason": "no such index", "resource.type": "index_or_alias", "resource.id": "product", "index_uuid": "_na_", "index": "product" }, "status": 404 }
4.4新建 ItemRepository
用於對 document 的操作測試
public interface ItemRepository extends ElasticsearchRepository<Item, Long>{ }
在 ItemTest 中注入 ItemRepository
@Autowired private ItemRepository itemRepository;
4.5新增文檔
修改和新增是同一個接口,區分的依據就是id,新增用POST 請求,修改用PUT請求。
@Test public void testSaveDocument(){ Item item = new Item(1L, "小米手機7", " 手機", "小米", 3499.00, "13123.jpg"); itemRepository.save(item); }
Kibana 通過GET product/_search 查詢
{ "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "product", "_type": "item", "_id": "1", "_score": 1, "_source": { "id": 1, "title": "小米手機7", "category": " 手機", "brand": "小米", "price": 3499, "images": "13123.jpg" } } ] } }
4.6批量新增
@Test public void testSaveDocumentList() { List<Item> list = new ArrayList<>(); list.add(new Item(1L, "小米手機7", "手機", "小米", 3299.00, "13123.jpg")); list.add(new Item(2L, "堅果手機R1", "手機", "錘子", 3699.00, "13123.jpg")); list.add(new Item(3L, "華為META10", "手機", "華為", 4499.00, "13123.jpg")); list.add(new Item(4L, "小米Mix2S", "手機", "小米", 4299.00, "13123.jpg")); list.add(new Item(5L, "榮耀V10", "手機", "華為", 2799.00, "13123.jpg")); // 接收對象集合,實現批量新增 itemRepository.saveAll(list); }
Kibana 通過GET product/_search 再次查詢,得到5個doc
4.7基本查詢
在 ElasticsearchRepository 繼承下來的查詢方法
4.7.1根據Id查詢
@Test public void testFindById(){ Optional<Item> optional = itemRepository.findById(1l); System.out.println(optional.get()); }
結果
4.7.2查詢所有
@Test public void testFindAll(){ // 查詢所有,並根據 price 降序排序 Iterable<Item> items = itemRepository.findAll(Sort.by(Sort.Direction.DESC,"price")); items.forEach(System.out::println); }
結果
4.8自定義方法
Spring Data 的提供一個強大功能,是根據方法名稱自動實現功能,下述自定義規范:
在ItemRepository定義一個方法findByPriceBetween,不用寫這個方法的實現例如:根據價格區間查詢所有 item
/**
* 根據價格區間查詢
* @param price1
* @param price2
* @return
*/
List<Item> findByPriceBetween(double price1, double price2);
在 ItemTest 編寫測試
@Test public void testFindByPriceBetween(){ List<Item> list = this.itemRepository.findByPriceBetween(4000.00, 5000.00); list.forEach(System.out::println); }
結果
4.9高級查詢
4.9.1基本查詢
Repository 的 search 方法,使用 QueryBuilders 構建查詢條件
QueryBuilders 提供了大量的靜態方法,用於生成各種不同類型的查詢對象,例如:詞條、模糊、通配符等QueryBuilder對象
public void testQuery(){ // 詞條查詢 MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米"); // 執行查詢 Iterable<Item> items = this.itemRepository.search(queryBuilder); items.forEach(System.out::println); }
結果
4.9.2自定義查詢
@Test public void testNativeQuery(){ // 構建查詢條件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 添加基本的分詞查詢 queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米")); // 執行搜索,獲取結果 Page<Item> items = this.itemRepository.search(queryBuilder.build()); // 打印總條數 System.out.println(items.getTotalElements()); // 打印總頁數 System.out.println(items.getTotalPages()); items.forEach(System.out::println); }
結果
NativeSearchQueryBuilder:Spring提供的一個查詢條件構建器,幫助構建json格式的請求體。
Page<item>:默認是分頁查詢,因此返回的是一個分頁的結果對象,包含屬性:
totalElements:總條數
totalPages:總頁數
Iterator:迭代器,本身實現了Iterator接口,因此可直接迭代得到當前頁的數據
4.9.3分頁查詢
利用NativeSearchQueryBuilder可以方便的實現分頁
@Test public void testNativePageQuery(){ // 構建查詢條件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 添加基本的分詞查詢 queryBuilder.withQuery(QueryBuilders.termQuery("category", "手機")); // 初始化分頁參數 int page = 0; int size = 3; // 設置分頁參數 queryBuilder.withPageable(PageRequest.of(page, size)); // 執行搜索,獲取結果 Page<Item> items = this.itemRepository.search(queryBuilder.build()); // 打印總條數 System.out.println("總條數:"+items.getTotalElements()); // 打印總頁數 System.out.println("總頁數:"+items.getTotalPages()); // 每頁大小 System.out.println("每頁大小:"+items.getSize()); // 當前頁 System.out.println("當前頁:"+items.getNumber()); items.forEach(System.out::println); }
結果:分頁是從第0頁開始
4.9.4排序
排序也通用通過NativeSearchQueryBuilder完成
@Test public void testSortQuery(){ // 構建查詢條件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 添加基本的分詞查詢 queryBuilder.withQuery(QueryBuilders.termQuery("category", "手機")); // 排序 queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); // 執行搜索,獲取結果 Page<Item> items = this.itemRepository.search(queryBuilder.build()); // 打印總條數 System.out.println("總條數:"+items.getTotalElements()); items.forEach(System.out::println); }
結果
4.10聚合
4.10.1普通聚合
按照品牌brand進行分組
@Test public void testBrandAgg(){ NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 不查詢任何結果 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); // 1、添加一個新的聚合,聚合類型為terms,聚合名稱為brands,聚合字段為brand queryBuilder.addAggregation( AggregationBuilders.terms("brands").field("brand")); // 2、查詢,需要把結果強轉為AggregatedPage類型 AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build()); // 3、解析 // 3.1、從結果中取出名為brands的那個聚合, // 因為是利用String類型字段來進行的term聚合,所以結果要強轉為StringTerm類型 StringTerms agg = (StringTerms) aggPage.getAggregation("brands"); // 3.2、獲取桶 List<StringTerms.Bucket> buckets = agg.getBuckets(); // 3.3、遍歷 for (StringTerms.Bucket bucket : buckets) { // 3.4、獲取桶中的key,即品牌名稱 和 文檔數量 System.out.println(bucket.getKeyAsString() + ":" +bucket.getDocCount()); } }
結果
AggregationBuilders.terms("brands").field("brand") 聚合的構建工廠類AggregationBuilders,所有聚合都由這個類來構建
aggPage.getAggregation("brands")返回的結果都是Aggregation類型對象,不過根據字段類型不同,又有不同的子類表示
4.10.2嵌套聚合
@Test public void testSubAvgAgg(){ NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 不查詢任何結果 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); // 1、添加一個新的聚合,聚合類型為terms,聚合名稱為brands,聚合字段為brand queryBuilder.addAggregation( AggregationBuilders.terms("brands").field("brand") .subAggregation(AggregationBuilders.avg("avgPrice").field("price")) // 在品牌聚合桶內進行嵌套聚合,求平均值 ); // 2、查詢,需要把結果強轉為AggregatedPage類型 AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build()); // 3、解析 // 3.1、從結果中取出名為brands的那個聚合, // 因為是利用String類型字段來進行的term聚合,所以結果要強轉為StringTerm類型 StringTerms agg = (StringTerms) aggPage.getAggregation("brands"); // 3.2、獲取桶 List<StringTerms.Bucket> buckets = agg.getBuckets(); // 3.3、遍歷 buckets.forEach(bucket -> { // 3.4、獲取桶中的key,即品牌名稱 ; 獲取桶中的文檔數量 ;獲取平均值結果: InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avgPrice"); System.out.println(bucket.getKeyAsString() + "共" + bucket.getDocCount() +",平均售價:"+ avg.getValue() ); }); }
結果