SpringData整合ElasticSearch

SpringData整合ElasticSearch

1 前言

很早很早以前就聽說了ElasticSearch,知道了它對大量數據的強悍搜索功能,當時就想以後一定要用一下這個東西,把它加到我自己的項目中。兜兜轉轉終於到了使用的這天,雖說有點大材小用,但還是在我的博客項目中使用了它。

2 ElasticSearch與MYSQL類比

ElasticSearch MYSQL
索引庫(indices) 數據庫(databases)
類型(type) 表(table)
文檔(document) 行(row)
字段(field) 列(column)

3 使用Docker安裝ElasticSearch

需要注意的是安裝ElasticSearch時要注意自己springboot引入的springdata引入的ElasticSearch版本

我的版本是2.4.6,所以我下載鏡像時也是下載的相應的版本。
Docker倉庫查看對應版本的鏡像,然後使用下面的命令下載

docker pull elasticsearch:2.4.6

下載完成後

然後啓動鏡像,指定運行內存最大與最小都爲256m,後臺運行,端口映射

docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01  5e9d896dc62c

訪問9200端口,啓動成功則會出現如下情景

4 創建SpringBoot項目整合

properties文件(cluster-name就是上面的cluster_name,一般默認是elasticsearch)

spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=118.25.236.51:9300

Image類

@Data
@Document(indexName = "blog",type = "image")
public class Image {

    private int id;
    private String url;
    private int type;

    @Field(type = FieldType.String,analyzer = "ik_max_word")
    private String tag;

    @Field(type = FieldType.String,analyzer = "ik_max_word")
    private String description;
}

Spring Data通過註解來聲明字段的映射屬性,有下面的三個註解:

  • @Document作用在類,標記實體類爲文檔對象,一般有兩個屬性
    • indexName:對應索引庫名稱
    • type:對應在索引庫中的類型
    • shards:分片數量,默認5
    • replicas:副本數量,默認1
  • @Id 作用在成員變量,標記一個字段作爲id主鍵
  • @Field 作用在成員變量,標記爲文檔的字段,並指定字段映射屬性:
    • type:字段類型,是枚舉:FieldType,可以是text、long、short、date、integer、object等
      • text:存儲數據時候,會自動分詞,並生成索引
      • keyword:存儲數據時候,不會分詞建立索引
      • Numerical:數值類型,分兩類
        • 基本數據類型:long、interger、short、byte、double、float、half_float
        • 浮點數的高精度類型:scaled_float
          • 需要指定一個精度因子,比如10或100。elasticsearch會把真實值乘以這個因子後存儲,取出時再還原。
      • Date:日期類型
        • elasticsearch可以對日期格式化爲字符串存儲,但是建議我們存儲爲毫秒值,存儲爲long,節省空間。
    • index:是否索引,布爾類型,默認是true
    • store:是否存儲,布爾類型,默認是false
    • analyzer:分詞器名稱,這裏的ik_max_word即使用ik分詞器(關於IK分詞器將在以後介紹)

5 繼承ElasticSearch的Repository接口完成基本的操作

ElasticsearchRepository接口

@NoRepositoryBean
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
    <S extends T> S index(S var1);

    Iterable<T> search(QueryBuilder var1);

    Page<T> search(QueryBuilder var1, Pageable var2);

    Page<T> search(SearchQuery var1);

    Page<T> searchSimilar(T var1, String[] var2, Pageable var3);

    void refresh();

    Class<T> getEntityClass();
}

繼承接口(同JpaRepository等接口相同,繼承接口就已經有實現的操作數據的方法)

public interface ImageRepository extends ElasticsearchRepository<Image,Integer> {
}

5.1 添加對象

	@Test
    public void addImage(){
        Image image = new Image();
        image.setId(4);
        image.setUrl("444");
        image.setTag("tag");
        image.setType(1);
        image.setDescription("aaaaaaa");
        imageRepository.save(image);
    }

5.2 刪除對象

  	@Test
    public void deleteImage(){
        imageRepository.delete(4);
    }

5.3 更新對象

	@Test
    public void update(){
        Image image = new Image();
        image.setId(4);//注意id應該與要更改的數據的相同
        image.setUrl("333");
        image.setTag("tag1");
        image.setType(1);
        image.setDescription("aaaaaaa");
        imageRepository.save(image);
    }

5.4 查詢對象

5.4.1 基本查詢(通過ElasticsearchRepository提供的方法進行查詢)
5.4.2 通過自己需求自定義方法
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 findByAvailableTrueOrderByNameDesc {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}
5.4.3 通過構建Elasticsearch的查詢語句來實現查詢
  • 重要查詢

match查詢

無論你在任何字段上進行的是全文搜索還是精確查詢,match 查詢是你可用的標準查詢。

如果你在一個全文字段上使用 match 查詢,在執行查詢前,它將用正確的分析器去分析查詢字符串:如果在一個精確值的字段上使用它, 例如數字、日期、布爾或者一個 not_analyzed 字符串字段,那麼它將會精確匹配給定的值。(查詢前可以分詞,可以不分詞,看類型)

 	@Test
    public void query(){
        String tag = "武器";
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        
        //查詢tag域中存在"武器"的數據
        queryBuilder.withQuery(QueryBuilders.matchQuery("tag", tag));
        Page<Image> search = imageRepository.search(queryBuilder.build());
        for (Image image : search) {
            System.out.println(image);
        }
    }

term查詢

term 查詢被用於精確值 匹配,這些精確值可能是數字、時間、布爾或者那些 not_analyzed 的字符串,term 查詢對於輸入的文本不 分析 ,所以它將給定的值進行精確查詢。

更多的組合語法請參考Elasticsearch查詢的官方文檔

	@Test
    public void query() {
        String tag = "武器";
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(QueryBuilders.termQuery("tag", tag));
        Page<Image> search = imageRepository.search(queryBuilder.build());
        for (Image image : search) {
            System.out.println(image);
        }
    }
  • 組合查詢

可以用 bool 查詢來實現組合查詢。這種查詢將多查詢組合在一起,成爲用戶自己想要的布爾查詢。它接收以下參數:

must

文檔 必須 匹配這些條件才能被包含進來。

must_not

文檔 必須不 匹配這些條件才能被包含進來。

should

如果滿足這些語句中的任意語句,將增加 _score ,否則,無任何影響。它們主要用於修正每個文檔的相關性得分。

filter

必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含文檔。

 public List<Image> combineQuery(String search) {
     
     	//組合查詢BoolQueryBuilder
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
     	//精確匹配tag,不分詞
     	//should相當於or,下面語句的意思是,找到tag域 或 description域中存在search的文檔
        boolQueryBuilder.should(QueryBuilders.termQuery("tag", search))
            
        //兩種條件會出現不同結果,具體看後面分析
//              .should(QueryBuilders.termQuery("description",search));
                .should(QueryBuilders.matchQuery("description", search));
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
     
     	//根據id域升序排列查詢結果
        NativeSearchQuery searchQuery = nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.ASC)).withQuery(boolQueryBuilder).build();

        Page<Image> search1 = imageRepository.search(searchQuery);
        List<Image> returnImages = new ArrayList<>();
        for (Image image : search1) {
            returnImages.add(image);
        }
        return returnImages;
    }

前面已經講過matchterm的區別,現在直觀的來看一下

description使用matchQuery


description使用matchQuery

description使用term

當使用term時,查詢字段不會分詞,直接是武器氣勢精確查詢,但是索引中卻不存在這個索引,真正的索引如下:

description的分詞索引

所以當使用term進行精確查詢時,返回結果就爲空。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章