天天看點

第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用

上一章:《第四章:SpringBoot整合ElasticSearch實作模糊查詢,批量CRUD,排序,分頁,高亮》

文章目錄

    • 5.1 ElasticSearchRepository的基本使用
    • 5.2 ElasticsearchRestTemplate的使用
    • 5.3 實戰
      • 1.實體類
      • 2.Elasticsearch Service
      • 3.查詢

在上一章節,我們學習到了es通過RestHighLevelClient實作最基本的增删改查的文法,在本章我們繼續深入實踐一下es的相關操作,在SpringBoot的相關依賴中,es已經幫我們将基本的操作都進行了封裝,我們隻需要掌握這些api,便可以輕松的操作我們的es

本章主角:

ElasticSearchRepository

ElasticsearchRestTemplate

Spring-data-elasticsearch是Spring提供的操作ElasticSearch的資料層,封裝了大量的基礎操作,通過它可以很友善的操作ElasticSearch的資料。

對于相關的依賴以及配置請參考第四章資訊

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

我們是通過繼承ElasticsearchRepository來完成基本的CRUD及分頁操作的,和普通的JPA沒有什麼差別。

我們使用方式也很簡單,寫一個接口然後繼承它即可

ElasticsearchRepository<,>
  • 第一個就是所準備的實體類
  • 第二個是id的類型
public interface EsUserService extends ElasticsearchRepository<User, Integer> {
}
           

然後便可以進行相關的增删改查以及批量操作

特殊情況下,ElasticsearchRepository裡面有幾個特殊的search方法,這些是ES特有的,和普通的JPA差別的地方,用來建構一些ES查詢的。

主要是看

QueryBuilder

SearchQuery

兩個參數,要完成一些特殊查詢就主要看建構這兩個參數。

我們先來看看它們之間的類關系

第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用

從這個關系中可以看到ES的search方法需要的參數SearchQuery是一個接口,有一個實作類叫NativeSearchQuery,實際使用中,

我們的主要任務就是建構NativeSearchQuery來完成一些複雜的查詢的

第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用

我們可以看到要建構NativeSearchQuery,主要是需要幾個構造參數

public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts, Field[] highlightFields) {  
    this.query = query;  
    this.filter = filter;  
    this.sorts = sorts;  
    this.highlightFields = highlightFields;  
}  
           

當然了,我們沒必要實作所有的參數。

可以看出來,大概是需要QueryBuilder,filter,和排序的SortBuilder,和高亮的字段。

一般情況下,我們不是直接是new NativeSearchQuery,而是使用NativeSearchQueryBuilder。

通過如下所示的方式來完成NativeSearchQuery的建構。

第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用

從名字就能看出來,QueryBuilder主要用來建構查詢條件、過濾條件,SortBuilder主要是建構排序。

譬如,我們要查詢距離某個位置100米範圍内的所有人、并且按照距離遠近進行排序:

double lat = 39.929986;  
double lon = 116.395645;  

Long nowTime = System.currentTimeMillis();  
//查詢某經緯度100米範圍内  
GeoDistanceQueryBuilder builder = QueryBuilders.geoDistanceQuery("address").point(lat, lon)  
    .distance(100, DistanceUnit.METERS);  
GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("address")  
    .point(lat, lon)  
    .unit(DistanceUnit.METERS)  
    .order(SortOrder.ASC);  
Pageable pageable = new PageRequest(0, 50);  
NativeSearchQueryBuilder builder1 = new NativeSearchQueryBuilder().withFilter(builder).withSort(sortBuilder).withPageable(pageable);  
SearchQuery searchQuery = builder1.build();  
           

要完成字元串的查詢:

要建構QueryBuilder,我們可以使用工具類QueryBuilders,裡面有大量的方法用來完成各種各樣的QueryBuilder的建構,字元串的、Boolean型的、match的、地理範圍的等等。

要建構SortBuilder,可以使用SortBuilders來完成各種排序。

然後就可以通過NativeSearchQueryBuilder來組合這些QueryBuilder和SortBuilder,再組合分頁的參數等等,最終就能得到一個SearchQuery了。

至此,我們明白了ElasticSearchRepository裡那幾個search查詢方法需要的參數的含義和建構方式了。

5.2 ElasticsearchRestTemplate的使用

ElasticSearchTemplate更多是對ESRepository的補充,裡面提供了一些更底層的方法。

原來的ElasticsearchTemplate已經過時了

這裡主要是一些查詢相關的,同樣是建構各種SearchQuery條件。

雖然ElasticsearchRestTemplate裡也包括save之類的JPA操作,但适用于小資料量,要完成超大資料的插入就要用ES自帶的bulk了,可以迅速插入百萬級的資料。

在ElasticsearchRestTemplate裡也提供了對應的方法

第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
public void bulkIndex(List<Person> personList) {  
    int counter = 0;  
    try {  
        if (!elasticsearchTemplate.indexExists(PERSON_INDEX_NAME)) {  
            elasticsearchTemplate.createIndex(PERSON_INDEX_TYPE);  
        }  
        List<IndexQuery> queries = new ArrayList<>();  
        for (Person person : personList) {  
            IndexQuery indexQuery = new IndexQuery();  
            indexQuery.setId(person.getId() + "");  
            indexQuery.setObject(person);  
            indexQuery.setIndexName(PERSON_INDEX_NAME);  
            indexQuery.setType(PERSON_INDEX_TYPE);  
            //上面的那幾步也可以使用IndexQueryBuilder來建構  
            //IndexQuery index = new IndexQueryBuilder().withId(person.getId() + "").withObject(person).build();  
            queries.add(indexQuery);  
            if (counter % 500 == 0) {  
                elasticsearchTemplate.bulkIndex(queries);  
                queries.clear();  
                System.out.println("bulkIndex counter : " + counter);  
            }  
            counter++;  
        }  
        if (queries.size() > 0) {  
            elasticsearchTemplate.bulkIndex(queries);  
        }  
        System.out.println("bulkIndex completed.");  
    } catch (Exception e) {  
        System.out.println("IndexerService.bulkIndex e;" + e.getMessage());  
        throw e;  
    }  
}  
           

這裡是建立了100萬個Person對象,每到500就用bulkIndex插入一次,速度飛快,以秒的速度插入了百萬資料。

講了那麼多理論,下面我們具體實踐一下

5.3 實戰

1.實體類

import lombok.Data;
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;

@Data
@Document(indexName = "user")//索引名稱 建議與實體類一緻
public class User {
    @Id
    private Integer id;
    @Field(type = FieldType.Auto)//自動檢測類型
    private Integer age;
    @Field(type = FieldType.Keyword)//手動設定為keyword  但同時也就不能分詞
    private String name;
    @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")//設定為text  可以分詞
    private String info;
}

           

如果沒有看過第三章:es的相關概念的話,這些注解看起來可能比較麻煩,為了節約大家時間,我把用到的相關概念在重複說一下

  • Keyword 用于索引結構化内容的字段,例如電子郵件位址,主機名,狀态代碼,郵政編碼或标簽。它們通常用于過濾,排序,和聚合。 Keyword 字段隻能按其确切值進行搜尋。
  • Text

    用于索引全文值的字段,例如電子郵件正文或産品說明。

    這些字段是被分詞的,它們通過分詞器傳遞 ,以在被索引之前将字元串轉換為單個術語的清單。

    分析過程允許 Elasticsearch 搜尋單個單詞中每個完整的文本字段。文本字段不用于排序,很少用于聚合。

  • IK提供兩種分詞ik_smart和ik_max_word,其中ik_smart為最少切分,ik_max_word為最細粒度劃分。

由于在本章之前還未教大家安裝ik分詞器,是以可以跳過它,暫時不用考慮

2.Elasticsearch Service

public interface EsUserService extends ElasticsearchRepository<User,Integer> {
    //根據name查詢
    List<User> findByName(String name);

    //根據name和info查詢
    List<User> findByNameAndInfo(String name,String info);
}
           

3.查詢

import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

@RestController
public class EsController {
    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;
    @Autowired
    private EsUserService esUserService;

    private String[] names = {"諸葛亮", "曹操", "李白", "韓信", "趙雲", "小喬", "狄仁傑", "李四", "諸小明", "王五"};
    private String[] infos = {"我來自中國的一個小鄉村,地處湖南省", "我來自中國的一個大城市,名叫上海,人們稱作魔都"
            , "我來自杭州,這是一個浪漫的城市"};

    /**
     * 儲存資料
     *
     * @return
     */
    @GetMapping("saveUser")
    public Object saveUser() {
        //添加索引mapping索引會自動建立但mapping自隻用預設的這會導緻分詞器不生效 是以這裡我們手動導入mapping
        Random random = new Random();
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            User user = new User();
            user.setId(i);
            user.setName(names[random.nextInt(9)]);
            user.setAge(random.nextInt(40) + i);
            user.setInfo(infos[random.nextInt(2)]);
            users.add(user);
        }
        Iterable<User> users1 = esUserService.saveAll(users);

        return users1;
    }

    /**
     * 通過id查詢資料
     *
     * @return
     */
    @GetMapping("getDataById")
    public Object getDataById(@RequestParam(value = "id") Integer id) {
        return esUserService.findById(id);
    }

    /**
     * 分頁查詢
     *
     * @return
     */
    @GetMapping("getAllDataByPage")
    public Object getAllDataByPage() {
        //本該傳入page和size,這裡為了友善就直接寫死了
        //表示通過Id正序排序
        Pageable page = PageRequest.of(0, 3, Sort.Direction.ASC, "id");
        Page<User> all = esUserService.findAll(page);
        return all.getContent();
    }

    /**
     * 根據名字查詢
     *
     * @param name
     * @return
     */
    @GetMapping("getDataByName")
    public Object getDataByName(@RequestParam(value = "name") String name) {
        return esUserService.findByName(name);
    }

    /**
     * 通過名字和Info取交集查詢
     *
     * @param name
     * @param info
     * @return
     */
    @GetMapping("getDataByNameAndInfo")
    public Object getDataByNameAndInfo(String name, String info) {
        //這裡是查詢兩個字段取交集,即代表兩個條件需要同時滿足
        return esUserService.findByNameAndInfo(name, info);
    }

    /**
     * 分詞高亮查詢
     *
     * @param value
     * @return
     */
    @GetMapping("getHightByUser")
    public Object getHightByUser(@RequestParam(value = "value") String value) {
        //根據一個值查詢多個字段  并高亮顯示  這裡的查詢是取并集,即多個字段隻需要有一個字段滿足即可
        //需要查詢的字段
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .should(QueryBuilders.matchQuery("info", value))
                .should(QueryBuilders.matchQuery("name", value));
        //建構高亮查詢
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withHighlightFields(
                        new HighlightBuilder.Field("info")
                        , new HighlightBuilder.Field("name"))
                .withHighlightBuilder(new HighlightBuilder().preTags("<span style='color:red'>").postTags("</span>"))
                .build();
        //查詢
        SearchHits<User> search = elasticsearchTemplate.search(searchQuery, User.class);
        //得到查詢傳回的内容
        List<SearchHit<User>> searchHits = search.getSearchHits();
        //設定一個最後需要傳回的實體類集合
        List<User> users = new ArrayList<>();
        //周遊傳回的内容進行處理
        for (SearchHit<User> searchHit : searchHits) {
            //高亮的内容
            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
            //将高亮的内容填充到content中
            searchHit.getContent().setName(highlightFields.get("name") == null ? searchHit.getContent().getName() : highlightFields.get("name").get(0));
            searchHit.getContent().setInfo(highlightFields.get("info") == null ? searchHit.getContent().getInfo() : highlightFields.get("info").get(0));
            //放到實體類中
            users.add(searchHit.getContent());
        }
        return users;
    }
}

           
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用
第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用

值得我們關注的是分詞高亮接口裡使用就是elasticsearchTemplate進行查詢的,如果我們想使用分頁,隻要在searchQuery添加:

第五章:ElasticSearchRepository和ElasticsearchRestTemplate的使用

自己可以在getHightByUser接口中修改嘗試一下

git位址:https://gitee.com/ninesuntec/es-better.git

繼續閱讀