7. SpringData操作ES
參考https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.mapping
7.1 創建項目、導入依賴、編寫配置文件
一定要注意版本的問題:版本不一致,會帶來很多問題:
Spring Data Elasticsearch | Elasticsearch | Spring Boot |
---|---|---|
3.2.x | 6.8.4 | 2.2.x |
3.1.x | 6.2.2 | 2.1.x |
3.0.x | 5.5.0 | 2.0.x |
2.1.x | 2.4.0 | 1.5.x |
首先導入關鍵dependency
<!--關鍵依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
# cluster-name不要寫錯
spring.data.elasticsearch.cluster-name=elasticsearch
# Java調用時端口是9300,restfu調用端口是9200,注意區分
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
7.2 編寫實體類型
然後編寫實體類型type
/**
* @author Honyelchak
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
// 若ES中沒有指定的索引,會自動創建。
// shards 分片數 replicas 副本數 refreshInterval 刷新間隔
@Document(indexName = "hello_world", type="user", shards = 2, replicas = 3)
public class Item implements Serializable {
@Id
private String id;
private String userName;
@Field(type = FieldType.Keyword)
private String passWord;
@Field(type = FieldType.Integer)
private Integer age;
// text 類型的會分詞
@Field(type = FieldType.Text, analyzer = "")
private String comment;
}
-
@Document(indexName = "hello_world",type = "user",shards = 5,replicas = 1)
: 標註在實體類上,聲明存儲的索引和類型剛纔, -
indexName
: 索引名稱type
:索引類型shards
:分片的數量,默認爲·5replicas
:副本的數量,默認爲1refreshInterval
: 刷新間隔,默認爲1s
indexStoreType
:索引文件存儲類型,默認爲fs
-
@Field
標註在屬性上,用來指定屬性的類型。其中的屬性如下: -
analyzer
:指定分詞器,es中默認使用的標準分詞器,比如我們需要指定中文IK分詞器,可以指定值爲ik_max_word
type
: 指定該屬性在es中的類型,其中的值是FileType
類型的值,比如FileType.Text
類型對應es中的text類型index
:指定該詞是否需要索引,默認爲truestore
:指定該屬性內容是否存儲在ES,默認爲falsefielddata
:指定該屬性能否進行排序,因爲es中的text類型是不能進行排序(已經分詞了)searchAnalyzer
: 指定搜索使用的分詞器name
:字段名稱,可以在ES文檔中表示,若未設置,則使用Java字段名稱。
7.3 使用方法:
-
直接在
dao
接口繼承ElasticsearchRepository
-
直接在
Service/Controller
中引入ElasticsearchTemplate
7.3.1 繼承ElasticsearchRepository
7.3.1.1 SpringData重要接口
Spring Data的中央接口是Repository
。
- 它需要實體類以及實體類的ID作爲類型參數來管理。
- 該接口主要用作標記接口(內無實現),以捕獲要使用的類型並幫助您發現擴展該接口的接口
CrudRepository
規定對於正在管理的實體類複雜的CRUD功能。
public interface CrudRepository<T, ID> 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.
}
ES中的PagingAndSortingRepository
接口 繼承了CrudRepository
。簡化了分頁操作和排序操作:
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
而在SpringBoot項目中,常用的是ElasticsearchRepository
,他在ElasticsearchCrudRepository
的基礎上補充了一些與查詢相關的方法。
7.3.1.2 根據方法名查詢
interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
}
7.3.1.3 @Query(json)查詢
上面的方法名稱將轉換爲以下Elasticsearch json查詢,
{
"query": {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}
}
還可以用@Query
註解來自定義查詢語句,例如:
@Query("{\n" +
" \"bool\": {\n" +
" \"must\": [\n" +
" {\n" +
" \"match\": {\n" +
" \"userName\": \"?0\"\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
" }")
List<User> selectByUserName(String userName);
PS: 其中的?是佔位符,0表示第一個參數
對應表:
關鍵詞 | 樣品 | Elasticsearch查詢字符串 |
---|---|---|
And |
findByNameAndPrice |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Or |
findByNameOrPrice |
{ "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Is |
findByName |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Not |
findByNameNot |
{ "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Between |
findByPriceBetween |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
LessThan |
findByPriceLessThan |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }} |
LessThanEqual |
findByPriceLessThanEqual |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
GreaterThan |
findByPriceGreaterThan |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }} |
GreaterThanEqual |
findByPriceGreaterThan |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Before |
findByPriceBefore |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
After |
findByPriceAfter |
{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Like |
findByNameLike |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
StartingWith |
findByNameStartingWith |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
EndingWith |
findByNameEndingWith |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
Contains/Containing |
findByNameContaining |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
In |
findByNameIn(Collectionnames) |
{ "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
NotIn |
findByNameNotIn(Collectionnames) |
{ "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
Near |
findByStoreNear |
Not Supported Yet ! |
True |
findByAvailableTrue |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }} |
False |
findByAvailableFalse |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] } |
7.3.2 引入ElasticsearchTemplate
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@GetMapping("createIndex")
public boolean createIndex(String indexName) {
return elasticsearchTemplate.createIndex(indexName);
}
@GetMapping("deleteIndex")
public boolean deleteIndex(String indexName) {
return elasticsearchTemplate.deleteIndex(indexName);
}
@GetMapping("indexIsExist")
public boolean indexIsExist(String indexName) {
return elasticsearchTemplate.indexExists(indexName);
}
@GetMapping("typeIsExist")
public boolean typeIsExist(String indexName, String type) {
return elasticsearchTemplate.typeExists(indexName, type);
}
@GetMapping("getMapping")
public Map getMapping(String indexName, String type) {
return elasticsearchTemplate.getMapping(indexName, type);
}
@GetMapping("getSetting")
public Map getSetting(String indexName) {
return elasticsearchTemplate.getSetting(indexName);
}
7.4 高級查詢
- QueryBuilders 構建查詢關鍵詞
- SortBuilders 構建對關鍵字的排序
- NativeSearchQueryBuilder 對前兩個條件進行封裝,
- Repository : 進行查詢
// 分詞查詢 :comment中包含中國的用戶,並且以age倒序返回。
@Test
public void searchByCommentSortByAge() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(QueryBuilders.termQuery("comment", "中國"));
FieldSortBuilder sortBuilder = SortBuilders.fieldSort("age").order(SortOrder.DESC);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
nativeSearchQueryBuilder.withSort(sortBuilder);
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
Page<Item> itemsPage = itemRepository.search(searchQuery);
if (itemsPage != null) {
List<Item> items = itemsPage.getContent();
items.forEach(System.out::println);
} else {
System.out.println("can not find that!");
}
}
// 查詢userName爲fanqi的用戶,並且以age倒序返回。
@Test
public void searchByUserNameSortByAge() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(QueryBuilders.termQuery("userName", "fanqi"));
FieldSortBuilder sortBuilder = SortBuilders.fieldSort("age").order(SortOrder.DESC);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
nativeSearchQueryBuilder.withSort(sortBuilder);
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
Page<Item> itemsPage = itemRepository.search(searchQuery);
if (itemsPage != null) {
List<Item> items = itemsPage.getContent();
items.forEach(System.out::println);
} else {
System.out.println("can not find that!");
}
}