ElasticSearch與SpringBoot的整合與JPA方法的使用

Eamon_Zzz發表於2019-01-03

完整程式碼示例,請參考個人GitHub倉庫:(github.com/KimZing), 包含controller/repository以及測試程式碼。

歡迎star,如有錯誤,歡迎指正_

一、環境簡介

  • idea 2016.3
  • jdk 1.8
  • ElasticSearch 2.4(之所以不用最新的,是因為SpringBoot和ES的版本是需要相匹配的,而SpringBoot Starter目前不支援最新版)

二、ES簡介

開發過Java搜尋的同學一定都知道lucene搜尋引擎,但是lucene只是一個搜尋引擎,就好比一個汽車的發動機,重要但是卻無法直接使用。 後來就有了為大家所知的solr搜尋,提供了對應的web操作介面和java api操作,但是solr的資料併發量和大資料量下的表現相比後來者ES 都還是有一定差距的,而且ES是天生支援分散式叢集的。

ES是什麼?我們可以把ES比作一個Mysql資料庫,同樣用來儲存資料,不過比Mysql提供了更多的搜尋功能,例如分詞搜尋,關聯度搜尋等,而且搜尋速度也不是同一級別的, ES能夠實現百萬資料/秒的查詢速度。接下來將ES中用到的概念和Mysql進行類比

欄位 解釋
index 索引,相當於Mysql中的一個庫,例如有一個叫『jd』的庫,那麼裡面可以建立很多表,儲存不同型別的資料,而表在ES中就是type。
type 型別,相當於Mysql中的一張表,儲存json型別的資料
document 文件,一個文件相當於Mysql一行的資料
shards 分片,通俗理解,就是資料分成幾塊區域來儲存,可以理解為mysql中的分庫分表(不太恰當)
replicas 備份,就是分片的備份數,相當於mysql的備份庫

ES使用json資料進行資料傳遞,例如{username:king,age:12},那麼這一整條json資料就是一個document,而username,age就是field。

三、SpringBoot整合ES的依賴

//ES的核心依賴Starter
compile('org.springframework.boot:spring-boot-starter-data-elasticsearch')
//jna依賴,否則專案啟動時,會報classNotFound: native method disable的錯誤
compile("com.sun.jna:jna:3.0.9")
//新增web支援,方便測試
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
複製程式碼

四、配置檔案

配置檔案是可以配置也可以不配置的,data-elasticsearch的依賴結構中已經包含了Lucene和ES的jar,SpringBoot會自動在本地給我們生成一個ES的倉庫,專案下會自動產生一個data資料夾儲存ES的資料。如果我們不配置ES例項, 那麼SpringBoot就會自動生成這個ES例項,當然效能肯定是不行的,所以我們還是使用自己搭建的ES例項。

data-elasticsearch的依賴結構

data-elasticsearch的依賴結構
連線獨立的ES例項的配置如下

spring:
  data:
    #ElasticSearch的連線地址
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: localhost:9300
複製程式碼

關於ES的安裝可以參考我的另一篇博文CentOS6.5安裝ES教程

五、編寫儲存實體類

編寫實體類主要會用到如下三個註解

1.類上註解:@Document (相當於Hibernate實體的@Entity/@Table) (必寫)

型別 屬性名 預設值 說明
String indexName 索引庫的名稱,建議以專案的名稱命名
String type "" 型別,建議以實體的名稱命名
short shards 5 預設分割槽數
short replica 1 每個分割槽預設的備份數
String refreshInterval "1s" 重新整理間隔
String indexStoreType "fs" 索引檔案儲存型別

2.主鍵註解:@Id (相當於Hibernate實體的主鍵@Id註解) (必寫)

只是一個標識,並沒有屬性。

3.屬性註解 @Field (相當於Hibernate實體的@Column註解)

@Field預設是可以不加的,預設所有屬性都會新增到ES中。

型別 屬性名 預設值 說明
FileType type FieldType.Auto 自動檢測屬性的型別
FileType index FieldIndex.analyzed 預設情況下分詞
boolean store false 預設情況下不儲存原文
String searchAnalyzer "" 指定欄位搜尋時使用的分詞器
String indexAnalyzer "" 指定欄位建立索引時指定的分詞器
String[] ignoreFields {} 如果某個欄位需要被忽略

4.實體類示例

@Data  //lombok註解,會自動生成setter/getter,需要引入lombok的包才能使用。
@Document(indexName = "shop", type = "user", refreshInterval = "0s")
public class User {

    @Id
    private Long id;

    private String username;

    private String realname;

    private String password;

    private Integer age;

    //這三個註解是為了前臺序列化java8 LocalDateTime使用的,需要引入jsr310的包才可以使用
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    private LocalDateTime birth;

}
複製程式碼

六、編寫倉庫

1.程式碼編寫

寫一個類繼承ElasticsearchRepository<T, ID>,需要寫兩個泛型,第一個代表要儲存的實體型別,第二個代表主鍵型別,例如寫一個User類的倉儲如下:

/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/11/27 下午10:10
 * @desc 使用者倉庫.
 */
public interface UserRepository extends ElasticsearchRepository<User, Long>{

}
複製程式碼

我們來看一下ElasticsearchRepository的繼承結構(如下),其實就可以發現仍然是JPA的一套Reposiroty,那我們其實就可以用JPA的一套介面操作進行資料的增刪改查, spring會自動根據方法名為我們生成對應的代理類去實現這些方法。

ElasticSearch與SpringBoot的整合與JPA方法的使用

2.CRUD基礎操作

先來看看ElasticsearchRepository已經實現的一些基礎方法,這些方法的名稱已經具有很好的說明解釋了,那麼大家自己看看,很容易就能理解

ElasticSearch與SpringBoot的整合與JPA方法的使用

3.稍微複雜操作

jpa自帶的這些方法肯定是不能滿足我們的業務需求的,那麼我們如何自定義方法呢?我們只要使用特定的單詞對方法名進行定義,那麼Spring就會對我們寫的方法名進行解析, 生成對應的例項進行資料處理,有木有很簡單?那麼接下來就使用Spring官方文件中的例項進行演示。

先來看下關鍵字的說明


關鍵字 使用示例 等同於的ES查詢
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(Collectionnames) {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}}
NotIn findByNameNotIn(Collectionnames) {“bool” : {“must_not” : {“bool” : {“should” : {“field” : {“name” : “?”}}}}}}
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}}}}

下面寫幾個示例進行演示,只把倉儲層的列出來了,整體執行是測試過的,沒問題,如果需要整體程式碼請到本文頂部的github倉庫檢視。

/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/11/27 下午10:10
 * @desc 使用者倉庫.
 */
public interface UserRepository extends ElasticsearchRepository<User, Long>{

    /**
     * 查詢使用者名稱為username的使用者
     * @param username
     * @return
     */
    List<User> findByUsername(String username);

    /**
     * 查詢使用者名稱為username並且真實姓名為realname的使用者
     * @param username
     * @param realname
     */
    List<User> findByUsernameAndRealname(String username, String realname);

    /**
     * 查詢使用者名稱為username或者姓名為realname的使用者
     */
    List<User> findByUsernameOrRealname(String username, String realname);

    /**
     * 查詢使用者名稱不是username的所有使用者
     * @param username
     * @return
     */
    List<User> findByUsernameNot(String username);


    /**
     * 查詢年齡段為ageFrom到ageTo的使用者
     * @param ageFrom
     * @param ageTo
     * @return
     */
    List<User> findByAgeBetween(Integer ageFrom, Integer ageTo);

    /**
     * 查詢生日小於birthTo的使用者
     */
    List<User> findByBirthLessThan(LocalDateTime birthTo);


    /**
     * 查詢生日段大於birthFrom的使用者
     * @param birthFrom
     * @return
     */
    List<User> findByBirthGreaterThan(LocalDateTime birthFrom);

    /**
     * 查詢年齡小於或等於ageTo的使用者
     */
    List<User> findByAgeBefore(Integer ageTo);

    /**
     * 查詢年齡大於或等於ageFrom的使用者
     * @param ageFrom
     * @return
     */
    List<User> findByAgeAfter(Integer ageFrom);

    /**
     * 使用者名稱模糊查詢
     * @param username
     * @return
     */
    List<User> findByUsernameLike(String username);


    /**
     * 查詢以start開頭的使用者
     * @param start
     * @return
     */
    List<User> findByUsernameStartingWith(String start);

    /**
     * 查詢以end結尾的使用者
     * @return
     */
    List<User> findByUsernameEndingWith(String end);

    /**
     * 查詢使用者名稱包含word的使用者
     * @param word
     * @return
     */
    List<User> findByUsernameContaining(String word);

    /**
     * 查詢名字屬於usernames中的使用者
     * @param usernames
     * @return
     */
    List<User> findByUsernameIn(Collection<String> usernames);

    /**
     * 查詢名字不屬於usernames中的使用者
     * @param usernames
     * @return
     */
    List<User> findByUsernameNotIn(Collection<String> usernames);

    /**
     *最後來個複雜點的:查詢年齡小於ageTo,姓名以start開頭,id大於idTo的使用者,並且按照年齡倒序
     * @return
     */
    List<User> findByAgeBeforeAndUsernameStartingWithAndIdGreaterThanOrderByAgeDesc(Integer ageTo, String start, Long idTo);

}
複製程式碼

4.更復雜一點的操作

我們可以使用@Query註解進行查詢,這樣要求我們需要自己寫ES的查詢語句,需要會ES查詢才可以,其實也很簡單,不會寫查就是了。 看看官方給的例子

public interface BookRepository extends ElasticsearchRepository<Book, String> {
        @Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}")
        Page<Book> findByName(String name,Pageable pageable);
}
複製程式碼

5.我們還可以使用類似Hibernate中criteria的方式進行查詢,

這時候我們需要自己寫查詢條件,在類中注入UserRepository,使用search方法傳入查詢引數,然後獲取查詢結果 示例如下:

/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/11/28 下午12:53
 * @desc 使用者服務.
 */
@Service
public class UserService {

    @Resource
    UserRepository userRepository;

    public Page<User> getUsers() {
        //建立builder
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        //builder下有must、should以及mustNot 相當於sql中的and、or以及not
        //設定模糊搜尋,真實姓名中包含金的使用者
        builder.must(QueryBuilders.fuzzyQuery("realname", "金"));
        //設定使用者名稱為king
        builder.must(new QueryStringQueryBuilder("king").field("username"));

        //排序
        FieldSortBuilder sort = SortBuilders.fieldSort("age").order(SortOrder.DESC);

        //設定分頁
        //====注意!es的分頁和Hibernate一樣api是從第0頁開始的=========
        PageRequest page = new PageRequest(0, 2);

        //構建查詢
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //將搜尋條件設定到構建中
        nativeSearchQueryBuilder.withQuery(builder);
        //將分頁設定到構建中
        nativeSearchQueryBuilder.withPageable(page);
        //將排序設定到構建中
        nativeSearchQueryBuilder.withSort(sort);
        //生產NativeSearchQuery
        NativeSearchQuery query = nativeSearchQueryBuilder.build();

        //執行,返回包裝結果的分頁
        Page<User> resutlList = userRepository.search(query);

        return resutlList;
    }
}
複製程式碼

作者:KimZing 來源:CSDN 原文:blog.csdn.net/KingBoyWorl…

相關文章