有關elasticsearch + kibana

Elasticsearch也使用Java開發並使用Lucene作爲其核心來實現所有索引和搜索的功能,但是它的目的是通過簡單的RESTful API來隱藏Lucene的複雜性,從而讓全文搜索變得簡單。

index ==》索引 ==》Mysql中的一個庫,庫裏面可以建立很多表,存儲不同類型的數據,而表在ES中就是type。

type ==》類型 ==》相當於Mysql中的一張表,存儲json類型的數據

document  ==》文檔 ==》一個文檔相當於Mysql一行的數據

field ==》列 ==》相當於mysql中的列,也就是一個屬性

 

二 springboot 對應的Es版本關係

springboot  elasticsearch
2.0.0.RELEASE 2.2.0
1.4.0.M1 1.7.3
1.3.0.RELEASE 1.5.2
1.2.0.RELEASE 1.4.4
1.1.0.RELEASE 1.3.2
1.0.0.RELEASE 1.1.1

三 環境構建

maven依賴:前提是依賴

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>1.5.9.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置文件:

# ES

#開啓 Elasticsearch 倉庫(默認值:true)

spring.data.elasticsearch.repositories.enabled=true

#默認 9300 是 Java 客戶端的端口。9200 是支持 Restful HTTP 的接口

spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

#spring.data.elasticsearch.cluster-name Elasticsearch 集羣名(默認值: elasticsearch)

#spring.data.elasticsearch.cluster-nodes 集羣節點地址列表,用逗號分隔。如果沒有指定,就啓動一個客戶端節點

#spring.data.elasticsearch.propertie 用來配置客戶端的額外屬性

#存儲索引的位置

spring.data.elasticsearch.properties.path.home=/data/project/target/elastic

#連接超時的時間

spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s

 

 四 es索引實體類

Spring-data-elasticsearch爲我們提供了@Document@Field等註解,如果某個實體需要建立索引,只需要加上這些註解即可

1.類上註解:@Document (相當於Hibernate實體的@Entity/@Table)(必寫),加上了@Document註解之後默認情況下這個實體中所有的屬性都會被建立索引、並且分詞。

類型 屬性名 默認值 說明
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中。

加上@Field之後,@document默認把所有字段加上索引失效,只有家@Field 纔會被索引(同時也看設置索引的屬性是否爲no)

 

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

五 相關查詢方法

 官網參考

  實現方式比較多,已經存在的接口,使用根據需要繼承即可:

  1、CrudRepository接口 

public interface CrudRepository<T, ID extends Serializable>
  extends Repository<T, ID> {

  <S extends T> S save(S entity);      

  Optional<T> findById(ID primaryKey); 

  Iterable<T> findAll();               

  long count();                        

  void delete(T entity);               

  boolean existsById(ID primaryKey);   

  // … more functionality omitted.
}

 

 

  2、PagingAndSortingRepository接口

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

 

 

 例子:

分頁:

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));

 

計數:

interface UserRepository extends CrudRepository<User, Long> {
  long countByLastname(String lastname);
}  


3.自定義查詢實現

只要使用特定的單詞對方法名進行定義,那麼Spring就會對我們寫的方法名進行解析, 

該機制條前綴find…Byread…Byquery…Bycount…By,和get…By從所述方法和開始分析它的其餘部分。

引入子句可以包含進一步的表達式,如Distinct在要創建的查詢上設置不同的標誌。然而,

第一個By作爲分隔符來指示實際標準的開始。在非常基礎的層次上,您可以定義實體屬性的條件並將它們與And和連接起來Or

interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

構建查詢屬性算法原理

如上例所示。在查詢創建時,確保解析的屬性託管類的屬性

但是,你也可以通過遍歷嵌套屬性來定義約束。

假設Person  x有一個Address和 ZipCode。在這種情況下,方法名稱爲

List<Person> findByAddressZipCode(ZipCode zipCode);

創建屬性遍歷x.address.zipCode

解析算法如下:

  1. 首先將整個part(AddressZipCode)作爲屬性進行解釋,然後檢查具有該名稱屬性的類。如果皮匹配成功,則使用該屬性。
  2. 如果不是屬性,則算法拆分從右側的駝峯部分頭部和尾部,並試圖找出相應的屬性,在我們的例子,AddressZipCode。如果算法找到具有該頭部的屬性,它將採用尾部並繼續從那裏構建樹,然後按照剛剛描述的方式分割尾部。
  3. 如果第一個分割不匹配,則算法將分割點移動到左側AddressZipCode)並繼續。

雖然這應該適用於大多數情況,但算法仍可能會選擇錯誤的屬性。假設這個Person類也有一個addressZip屬性。

該算法將在第一輪拆分中匹配,並且基本上選擇錯誤的屬性並最終失敗(因爲addressZip可能沒有code屬性的類型)。

爲了解決這個歧義,你可以在你的方法名稱中使用手動定義遍歷點。所以我們的方法名稱會像這樣結束:

List<Person> findByAddress_ZipCode(ZipCode zipCode)由於我們將下劃線視爲保留字符,因此我們強烈建議遵循標準的Java命名約定(即,不要在屬性名稱中使用下劃線,而應使用駝峯大小寫)

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);



//也可以用Java8 Stream查詢和sql語句查詢

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
 
Stream<User> readAllByFirstnameNotNull();
 
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

有些在複雜的可以使用es查詢語句

我們可以使用@Query註解進行查詢,這樣要求我們需要自己寫ES的查詢語句


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

方法和es查詢轉換:

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}}}}

 

六 between使用注意

  在使用的時候沒有找到直接的例子,由於between是轉換成range,所以需要範圍參數from和to

 舉例如下:

Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(
String recruitWork, 
String recruitCitys, 
Integer workType, 
Date fromXjTime, 
Date toXjTime,
Pageable pageable);

注意:

只要使用了between參數,****XjTimeBetween(......,from,to) 必須要傳遞範圍參數from,to,不能同時爲空

否則異常:因爲底層要求參數不能同時爲空

七  es時間類型注意

來源: http://www.cnblogs.com/guozp/p/8686904.html 

對於Elasticsearch原生支持date類型,json格式通過字符來表示date類型。所以在

用json提交日期至elasticsearch的時候,es會隱式轉換,把es認爲是date類型的字符串直接轉爲date類型

字段內容實際上就是轉換成long類型作爲內部存儲的(所以完全可以接受其他時間格式作爲時間字段的內容)。至於什麼樣的字符串es會認爲可以轉換成date類型,參考elasticsearch官網介紹

date類型是包含時區信息的,如果我們沒有在json代表日期的字符串中顯式指定時區,對es來說沒什麼問題,但是對於我們來說可能會發現一些時間差8個小時的問題。

Elastic本身的時間格式爲ISO8601標準,其形式如"2016-01-25T00:00:00"。具體時間日期格式要求可以參見es官方文檔

我們在計算日期間隔,甚至按日分類的時候,往往需要把這個String時間轉化爲Unix時間戳(Unix Timestamp(時間戳))的形式,再進行計算。

而通常,這個時間戳會以毫秒的形式(Java)保存在一個long類型裏面,這就涉及到了String與long類型的相互轉化。

此外在使用Java Client聚合查詢日期的時候,需要注意時區問題,因爲默認的es是按照UTC標準時區算的,所以不設置的聚合統計結果是不正確的。默認不設置時區參數,es是安裝UTC的時間進行查詢的,所以分組的結果可能與預期不一樣。  

JSON 沒有日期類型,因此在 Elasticsearch 中可以表達成:

  1. 日期格式化的字符串,比如: "2018-01-01" 或者 "2018/01/01 01:01:30";
  2. 毫秒級別的 long 類型或秒級別的 integer 類型,比如: 1515150699465, 1515150699;

實際上不管日期以何種格式寫入,在 ES 內部都會先穿換成 UTC 時間並存儲爲 long 類型。日期格式可以自定義,如果沒有指定的話會使用以下的默認格式:

  "strict_date_optional_time||epoch_millis"

 

  因此總結來說,不管哪種可以表示時間的格式寫入,都可以用來表示時間

所以這裏引出多種解決方案:

  1. es 默認的是 utc 時間,而國內服務器是 cst 時間,首先有時間上的差距需要轉換。但是如果底層以及上層都統一用時間戳,完美解決時區問題。但是時間戳對我們來說不直觀 
  2. 我們在往es提交日期數據的時候,直接提交帶有時區信息的日期字符串,如:“2016-07-15T12:58:17.136+0800”
  3. 直接設置format爲你想要的格式,比如 "yyyy-MM-dd HH:mm:ss" 然後存儲的時候,指定格式,並且Mapping  也是指定相同的format 。
@Field( 
    type = FieldType.Date,
    format = DateFormat.custom,
    pattern = "date_optional_time"
)
private Date gmtCreate;

我這裏是數據是從數據庫直接讀取,使用的datetime類型,原來直接使用的時候,拋異常

  MapperParsingException[failed to parse [***]]; nested: IllegalArgumentException[Invalid format: "格式"];

原因: jackson庫在轉換爲json的時候,將Date類型轉爲爲了long型的字符串表示,而我們定義的是date_optional_time格式的字符串,所以解析錯誤,

解決辦法

去掉註解中的format=DateFormat.date_optional_time

讓其使用默認的格式,也就是 'strict_date_optional_time||epoch_millis' , 既能接受 date_optional_time格式的,也能接受epoch_millis格式,由於爲了查看更直觀感受改爲如下:

 @Field( 
        type = FieldType.Date,
        format = DateFormat.custom,
        pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
  )
private Date gmtCreate;

 改成這樣後,底層多種格式都可以存儲,如果沒有根據時間進行範圍查找,這裏基本上已經就告一段落了。

時間範圍查找需求:存儲Date,和取出來也是Date

存儲的時候利用各種JSON對象,如 Jackson 等。存儲的時候就可以用JSON Format一下再存儲,然後取出來後

用@jsonFormat註解,有了這個註解後,

    timezone="GMT+8" 主要是因爲底層存放的數據日期時區是UTC,這裏轉換成GMT
@Field( 
    type = FieldType.Date,
    format = DateFormat.custom,
    pattern = "yyyy-MM-dd HH:mm:ss"
)
@JsonFormat (
    shape = JsonFormat.Shape.STRING, 
    pattern ="yyyy-MM-dd HH:mm:ss",
    timezone="GMT+8"
)
    private Date xjTime;

時間範圍查找需求注意:

    根據條件查詢的時候,時間範圍需要傳入range,這裏涉及到了兩種選擇,底層查詢方法實現的時候range的參數爲

 

 

解決:

辦法1.傳入的date參數格式化成底層的類型 

實現參考:

Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(
String recruitWork, 
String recruitCitys, 
Integer workType, 
Date fromXjTime, 
Date toXjTime,
Pageable pageable);

辦法2:參數直接使用string,避免上層轉換成不合適的時間格式,使用框架底層自己轉換,避免錯誤。

實現參考:

Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(
String recruitWork, 
String recruitCitys, 
Integer workType, 
String fromXjTime, 
String toXjTime,
Pageable pageable);

八 使用注意

  個人認爲springboot 這種集成es的方法,最大的優點是開發速度快,不要求對es一些api要求熟悉,能快速上手,即使之前對es不勝瞭解,也能通過方法名或者sql快速寫出自己需要的邏輯,而具體轉換成api層的操作,則有框架底層幫你實現。

  缺點也顯而易見首先,使用的springboot的版本對es的版本也有了要求,不能超過es的某些版本號,部署時需要注意。第二,速度提升的同時,也失去了一些實用api的靈活性。一些比較靈活的條件封裝不能很容易的實現。各有利弊,各位權衡

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