我的ElasticSearch 版本是5.6.16
一. pom依賴
鑑於可能有版本衝突問題,我全貼上來。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.boot</groupId>
<artifactId>es</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二. Spring-data-elasticsearch 都提供了什麼
1. 方便的註解,讓創建索引更簡單
例如下面創建User的索引,創建的索引名是user_index,類型是user,主鍵是no, name和word兩個字段使用ik_max_word分詞器進行分詞。
package com.boot.es.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.MultiField;
/**
* Author: susq
* Date: 2019-06-10 23:44
*/
@Data
@Document(indexName = "user_index", type = "user", replicas = 0)
public class User {
@Id
private Long no;
@Field(type = FieldType.Integer)
private int age;
@MultiField(
mainField = @Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard", store = true),
otherFields = @InnerField(type = FieldType.Keyword, suffix = "raw"))
private String name;
@Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard")
private String word;
}
這樣創建索引的時候,直接根據類來創建就可以:
package com.boot.es;
import com.boot.es.model.User;
import com.boot.es.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Author: susq
* Date: 2019-06-26 00:09
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsServiceTests {
@Autowired
private ElasticsearchTemplate template;
@Autowired
private UserService userService;
@Test
public void rebuildIndex() {
template.createIndex(User.class);
log.info("user索引在嗎" + template.indexExists(User.class));
template.deleteIndex(User.class);
log.info("user索引在嗎" + template.indexExists(User.class));
}
}
對用到的註解做一下補充:
ublic @interface Document {
String indexName(); //索引庫的名稱
String type() default ""; //類型
short shards() default 5; //默認分區數
short replicas() default 1; //每個分區默認的備份數,我們上面設置0是因爲是單機的,在同一臺機器上備份沒用,才設置了0, 否則通過elastic-head插件,會出現UNASSIGNED節點。 elasticsearch-head插件使用參考我之前的文章:https://blog.csdn.net/u013041642/article/details/91355916
String refreshInterval() default "1s"; //刷新間隔
String indexStoreType() default "fs"; //索引文件存儲類型
boolean creatIndex() default true; // 是否給當前使用註解的對象自動創建索引
}
public @interface Field {
FieldType type() default FieldType.Auto; //自動檢測屬性的類型,可以根據實際情況自己設置
FieldIndex index() default true; //默認情況下分詞,一般默認分詞就好,除非這個字段你確定查詢時不會用到,可以設置成false讓它不分詞
DateFormat format() default DateFormat.none; //時間類型的格式化
String pattern() default "";
boolean store() default false; //默認情況下不存儲原文
String searchAnalyzer() default ""; //指定字段搜索時使用的分詞器
String indexAnalyzer() default ""; //指定字段建立索引時指定的分詞器
String[] ignoreFields() default {}; //如果某個字段需要被忽略
boolean includeInParent() default false;
}
2. 封裝好的接口,簡單的增刪改查功能都已經實現,如果功能簡單,我們幾乎不需要自己寫什麼代碼
package com.boot.es.repository;
import com.boot.es.model.User;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
// 兩個泛型,第一個是索引對應的實體類User,第二個是主鍵的類型
@Repository
public interface UserRepository extends ElasticsearchRepository<User, Long> {
}
繼承層次中,CrudRepository接口定義好了許多接口, 註釋我刪掉了,大家看源碼的話有很多註釋,比較簡單。
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
繼承層次中,還有個接口, 如果我們要構建複雜的查詢,可以自行拼裝QueryBuilder,
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
<S extends T> S index(S entity);
Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);
void refresh();
Class<T> getEntityClass();
}
3. 配合查詢的QueryBuilder, 提供了豐富的靜態方法,自由組合查詢條件,下面列舉了一些常用的。
這裏使用的標準分詞器,對漢字分詞不友好,所以這裏的name,word, 我都寫成了英文形式,後面會專門整理一篇漢字和拼音分詞器的。
package com.boot.es;
import com.boot.es.model.User;
import com.boot.es.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void testCurd() {
// 添加,可以saveAll批量添加
User user = new User();
user.setAge(26);
user.setName("zhangsan");
user.setNo(1L);
user.setWord("good good study!");
userRepository.save(user);
// 查詢全部
List<User> users = Lists.newArrayList(userRepository.findAll());
log.info("users,{}", users);
// 更新就是重複保存,有變化的字段會覆蓋老的
user.setWord("day day up!");
userRepository.save(user);
user = userRepository.findById(1L).orElse(null);
log.info("user,{}", user);
userRepository.deleteById(1L);
user = userRepository.findById(1L).orElse(null);
log.info("user,{}", user);
User user1 = new User();
user1.setAge(26);
user1.setName("zhang san");
user1.setNo(1L);
user1.setWord("good good study!");
User user2 = new User();
user2.setAge(36);
user2.setName("li si");
user2.setNo(2L);
user2.setWord("day day up!");
userRepository.saveAll(Lists.newArrayList(user1, user2));
// 查詢全部
users = Lists.newArrayList(userRepository.findAll());
log.info("users,{}", users);
}
@Test
public void testSearch() {
// 空條件查詢,返回全部
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
Iterable<User> users = userRepository.search(queryBuilder.build());
List<User> userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
//=========================精確匹配查詢==========================================================
// no等於1的,name用標準分詞器分詞後的倒排索引包含"li"的,name不分詞時候等於"li si"的,也可以同時都組合起來,則查詢條件就是要同時滿足的
// queryBuilder.withQuery(QueryBuilders.termQuery("no", 1L));
// queryBuilder.withQuery(QueryBuilders.termQuery("name", "li"));
// queryBuilder.withQuery(QueryBuilders.termQuery("name.raw", "li si"));
queryBuilder.withQuery(QueryBuilders.termQuery("age", 26));
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
//=============================檢索==============================================================
queryBuilder.withQuery(QueryBuilders.matchQuery("name", "zhang")); // 檢索name 中有"zhang"的
queryBuilder.withQuery(QueryBuilders.multiMatchQuery("good", "name", "word")); // 檢索name 或者 word 中有"good"的
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
/**
* 我們上面創建的索引,name,word兩個字段都設置分詞,name用漢字分詞器分詞,word用拼音分詞器分詞,
* termQuery() term過濾器會查找我們設定的準確值。如果有分詞的話,term過濾器只能匹配到分詞後的倒排索引。
* matchQuery()
* 1.被查字段是分詞的,那麼查詢字符串也要被分詞
* 2. 類似低級別的term查詢
* 3. 綜合考慮詞頻,逆文檔頻率,包含關鍵字的字段長度,來計算相關性打分_score
* multiMatchQuery() 可以多詞查詢,一段文本可以匹配多個字段,或的關係
* 在本文最後我也補充了例子,比較直觀
*/
//=============================組合查詢條件=======================================================
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "zhang"))
.mustNot(QueryBuilders.matchQuery("name", "li"))
.should(QueryBuilders.matchQuery("word", "good"))
.should(QueryBuilders.matchQuery("word", "up"))
.minimumShouldMatch(1);
queryBuilder.withQuery(boolQueryBuilder);
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
//=============================組合查詢條件自定義每個條件的權重,按權重排序=============================
BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery()
.should(QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("word", "good")).boost(2))
.should(QueryBuilders.functionScoreQuery(QueryBuilders.matchQuery("word", "up")).boost(1));
queryBuilder.withQuery(boolQueryBuilder1).withSort(SortBuilders.scoreSort());
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
/**
* 調整兩個boost的大小,最後打印的順序就會改變
* userList:[User(no=2, age=26, name=li si, word=day day up!), User(no=1, age=26, name=zhang san, word=good good study!)]
* userList:[User(no=1, age=26, name=zhang san, word=good good study!), User(no=2, age=26, name=li si, word=day day up!)]
*/
//======================================= 帶過濾條件的查詢==========================================
queryBuilder.withFilter(QueryBuilders.rangeQuery("age").lt(35)); // 只要年齡小於35的
users = userRepository.search(queryBuilder.build());
userList = Lists.newArrayList(users);
log.info("userList:{}", userList);
}
}
補充:
-
should: 所有的must子句必須匹配,所有的must_not 必須不匹配。默認的,不需要should匹配,但是,如果沒有must語句,則至少需要一個should匹配。像我們控制match查詢精度一樣,我們也可以通過minimum_should_match 參數控制多少should子句需要被匹配,這個參數可以是整數,也可以是百分比。
-
match在匹配時會對所查找的關鍵詞進行分詞,然後按分詞匹配查找,而term會直接對關鍵詞進行查找。一般模糊查找的時候,多用match,而精確查找時可以使用term。
舉個例子說明一下:
{ "match": { "title": "my cat"} } { "bool": { "should": [ { "term": { "title": "my" }}, { "term": { "title": "cat" }} ] } }
match 會將關鍵詞進行分詞分成“my”和“cat”,查找時包含其中任一均可被匹配到。
term結合bool使用,不進行分詞,但是有2個關鍵詞,並且使用“或”匹配,也就是會匹配關鍵字一“my”或關鍵字“cat”,效果和上面的match是相同的。如果要想精確的匹配“my cat”而不匹配“my lovely cat”,則可以如下方式匹配:
{ "bool": { "should": [ { "term": { "title": "my cat" }} ] } }