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分詞器將在以後介紹)
- type:字段類型,是枚舉:FieldType,可以是text、long、short、date、integer、object等
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;
}
前面已經講過match
與term
的區別,現在直觀的來看一下
description使用matchQuery
description使用matchQuery
description使用term
當使用term時,查詢字段不會分詞,直接是武器氣勢
精確查詢,但是索引中卻不存在這個索引,真正的索引如下:
description的分詞索引
所以當使用term
進行精確查詢時,返回結果就爲空。