SpringBoot 集成 Elasticsearch


前面在 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 再次查詢,得到5doc

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() );
    });
}

結果

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM