完整程式碼示例,請參考個人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的依賴結構
連線獨立的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會自動根據方法名為我們生成對應的代理類去實現這些方法。
2.CRUD基礎操作
先來看看ElasticsearchRepository已經實現的一些基礎方法,這些方法的名稱已經具有很好的說明解釋了,那麼大家自己看看,很容易就能理解
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…