摘要: 原創出處 http://www.iocoder.cn/Spring-Boot/Elasticsearch/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!
3. Spring Data Elasticsearch本文在提供完整代碼示例,可見 https://github.com/YunaiV/SpringBoot-Labs 的 lab-15 目錄。
原創不易,給點個 Star 嘿,一起衝鴨!
1. 概述如果胖友之前有用過 Elasticsearch 的話,可能有過被使用的 Elasticsearch 客戶端版本搞死搞活。如果有,那麼一起握個抓。所以,我們在文章的開始,先一起理一理這塊。
Elasticsearch(ES)提供了兩種連接方式:
transport :通過 TCP 方式訪問 ES 。
對應的庫是 org.elasticsearch.client.transport 。
rest :通過 HTTP API 方式訪問 ES 。
對應的庫是:
elasticsearch-rest-client + org.elasticsearch.client.rest,提供 low-level rest API 。elasticsearch-rest-high-level-client ,提供 high-level rest API 。從 Elasticsearch 6.0.0-beta1 開始提供。如果想進一步了解上述的 3 個 ES 客戶端,可以看看 《Elasticsearch 客戶端 transport vs rest》 文章。
雖然說,ES 提供了 2 種方式,官方目前建議使用 rest 方式,而不是 transport 方式。並且,transport 在未來的計劃中,準備廢棄。並且,阿里雲提供的 Elasticsearch 更加乾脆,直接只提供 rest 方式,而不提供 transport 方式。
在社區中,有個 Jest 開源項目,也提供了的 Elasticsearch REST API 客戶端。
參考 《ES Java Client 的歷史》
Elasticsearch 5.0 才有了自己的 Rest 客戶端 ,6.0 才有了更好用的客戶端,所以 Jest 作為第三方客戶端,使用非常廣泛。
正如我們在項目中,編寫資料庫操作的邏輯,使用 MyBatis 或者 JPA 為主,而不使用原生的 JDBC 。那麼,我們在編寫 Elasticsearch 操作的邏輯,也不直接使用上述的客戶端,而是:
spring-data-elasticsearch ,基於 Elasticsearch transport 客戶端封裝。spring-data-jest ,基於 Jest 客戶端封裝。雖然這兩者底層使用的不同客戶端,但是都基於 Spring Data 體系,所以項目在使用時,編寫的代碼是相同的。也因此,如果胖友想從 spring-data-elasticsearch 遷移到 spring-data-jest 時,基本透明無成本,美滋滋~
😈 這樣一個梳理,胖友是不是對 Elasticsearch 客戶端的選用,心裡已經明明白白落。嘿嘿。
下面,艿艿先來講一個悲傷的故事。大體過程是這樣的:
1、在上線的時候,發現線上使用的是阿里雲的 Elasticsearch 服務,不提供 transport 連接方式,而項目中使用的是 spring-data-elasticsearch 。所以,先臨時在 ECS 搭建一個 Elasticsearch 服務,過度下。2、然後,了解到有 Jest 客戶端可以使用 rest 連接方式,於是將操作 Elasticsearch 的代碼,全部改成 Jest 的方式。3、之後,發現竟然還有 spring-data-jest ,一臉悲傷,重新將操作 Elasticsearch 的代碼,全部改成 spring-data-jest 的方式。淚崩。所以胖友可知,艿艿目前線上使用 spring-data-jest 訪問 Elasticsearch 。
下面,我們分別來入門:
Spring Data Elasticsearch艿艿:如果胖友還沒安裝 Elasticsearch ,並安裝 IK 插件,可以參考下 《Elasticsearch 極簡入門》 文章,先進行下安裝。
2. Spring Data Jest示例代碼對應倉庫:lab-15-spring-data-jest 。
spring-boot-starter-data-jest :3.2.5.RELEASE本小節,我們會使用 spring-boot-starter-data-jest 自動化配置 Spring Data Jest 主要配置。同時,編寫相應的 Elasticsearch 的 CRUD 操作。
2.1 引入依賴在 pom.xml 文件中,引入相關依賴。
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-15-spring-data-jest</artifactId>
<dependencies>
<!-- 自動化配置 Spring Data Jest -->
<dependency>
<groupId>com.github.vanroy</groupId>
<artifactId>spring-boot-starter-data-jest</artifactId>
<version>3.2.5.RELEASE</version>
</dependency>
<!-- 方便等會寫單元測試 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>具體每個依賴的作用,胖友自己認真看下艿艿添加的所有注釋噢。
2.2 Application創建 Application.java 類,配置 @SpringBootApplication 註解即可。代碼如下:
// Application.java
@SpringBootApplication(exclude = {ElasticsearchAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class})
public class Application {
}
需要排除 ElasticsearchAutoConfiguration 和 ElasticsearchDataAutoConfiguration 自動配置類,否則會自動配置 Spring Data Elasticsearch 。2.3 配置文件在 application.yml 中,添加 Jest 配置,如下:
spring:
data:
# Jest 配置項
jest:
uri: http://127.0.0.1:9200
我們使用本地的 ES 服務。默認情況下,ES rest 連接方式暴露的埠是 9200 。2.4 ESProductDO在 cn.iocoder.springboot.lab15.springdatajest.dataobject 包路徑下,創建 ESProductDO 類。代碼如下:
// ESProductDO.java
@Document(indexName = "product", // 索引名
type = "product", // 類型。未來的版本即將廢棄
shards = 1, // 默認索引分區數
replicas = 0, // 每個分區的備份數
refreshInterval = "-1" // 刷新間隔
)
public class ESProductDO {
/**
* ID 主鍵
*/
@Id
private Integer id;
/**
* SPU 名字
*/
@Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
private String name;
/**
* 賣點
*/
@Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
private String sellPoint;
/**
* 描述
*/
@Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
private String description;
/**
* 分類編號
*/
private Integer cid;
/**
* 分類名
*/
@Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
private String categoryName;
// 省略 setting/getting 方法
}
為了區別關係資料庫的實體對象,艿艿習慣以 ES 前綴開頭。欄位上的 @Field 註解的 FieldAnalyzer ,是定義的枚舉類,記得自己創建下噢。代碼如下:// FieldAnalyzer.java
再友情提示下,一定要記得給 Elasticsearch 安裝 IK 插件,不然等會示例會報錯的喲。2.5 ProductRepository
public class FieldAnalyzer {
/**
* IK 最大化分詞
*
* 會將文本做最細粒度的拆分
*/
public static final String IK_MAX_WORD = "ik_max_word";
/**
* IK 智能分詞
*
* 會做最粗粒度的拆分
*/
public static final String IK_SMART = "ik_smart";
}在 cn.iocoder.springboot.lab15.mybatis.repository 包路徑下,創建 ProductRepository 接口。代碼如下:
// ProductRepository.java
public interface ProductRepository extends ElasticsearchRepository<ESProductDO, Integer> {
}
繼承 org.springframework.data.elasticsearch.repository.ProductRepository 接口,第一個泛型設置對應的實體是 ESProductDO ,第二個泛型設置對應的主鍵類型是 Integer 。因為實現了 ElasticsearchRepository 接口,Spring Data Jest 會自動生成對應的 CRUD 等等的代碼。😈 是不是很方便。ElasticsearchRepository 類圖如下:每個接口定義的方法,胖友可以點擊下面每個連結,自己瞅瞅,簡單~org.springframework.data.repository.CrudRepositoryorg.springframework.data.repository.PagingAndSortingRepositoryorg.springframework.data.elasticsearch.repository.ElasticsearchCrudRepositoryorg.springframework.data.elasticsearch.repository.ElasticsearchRepository艿艿:如果胖友看過艿艿寫的 《芋道 Spring Boot JPA 入門》 文章,會發現和 Spring Data JPA 的使用方式,基本一致。這就是 Spring Data 帶給我們的好處,使用相同的 API ,統一訪問不同的數據源。o( ̄▽ ̄)d 點讚。
2.6 簡單測試創建 ProductRepositoryTest 測試類,我們來測試一下簡單的 ProductRepositoryTest 的每個操作。代碼如下:
// ProductRepositoryTest.JAVA
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepositoryTest {
@Autowired
private ProductRepository productRepository;
@Test // 插入一條記錄
public void testInsert() {
ESProductDO product = new ESProductDO();
product.setId(1); // 一般 ES 的 ID 編號,使用 DB 數據對應的編號。這裡,先寫死
product.setName("芋道源碼");
product.setSellPoint("願半生編碼,如一生老友");
product.setDescription("我只是一個描述");
product.setCid(1);
product.setCategoryName("技術");
productRepository.save(product);
}
// 這裡要注意,如果使用 save 方法來更新的話,必須是全量欄位,否則其它欄位會被覆蓋。
// 所以,這裡僅僅是作為一個示例。
@Test // 更新一條記錄
public void testUpdate() {
ESProductDO product = new ESProductDO();
product.setId(1);
product.setCid(2);
product.setCategoryName("技術-Java");
productRepository.save(product);
}
@Test // 根據 ID 編號,刪除一條記錄
public void testDelete() {
productRepository.deleteById(1);
}
@Test // 根據 ID 編號,查詢一條記錄
public void testSelectById() {
Optional<ESProductDO> userDO = productRepository.findById(1);
System.out.println(userDO.isPresent());
}
@Test // 根據 ID 編號數組,查詢多條記錄
public void testSelectByIds() {
Iterable<ESProductDO> users = productRepository.findAllById(Arrays.asList(1, 4));
users.forEach(System.out::println);
}
}具體的,胖友可以自己跑跑,妥妥的。
3. Spring Data Elasticsearch示例代碼對應倉庫:lab-15-spring-data-elasticsearch 。
spring-boot-starter-data-elasticsearch :2.1.3.RELEASE本小節,我們會使用 spring-boot-starter-data-elasticsearch 自動化配置 Spring Data Elasticsearch 主要配置。同時,編寫相應的 Elasticsearch 的 CRUD 操作。
重點是,我們希望通過本小節,讓胖友感受到,Spring Data Jest 和 Spring Data Elasticsearch 是基本一致的。
3.1 引入依賴在 pom.xml 文件中,引入相關依賴。
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-15-spring-data-elasticsearch</artifactId>
<dependencies>
<!-- 自動化配置 Spring Data Elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 方便等會寫單元測試 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
差異點,就是將依賴 spring-boot-starter-data-jest 替換成 spring-boot-starter-data-elasticsearch 。具體每個依賴的作用,胖友自己認真看下艿艿添加的所有注釋噢。
3.2 Application創建 Application.java 類,配置 @SpringBootApplication 註解即可。代碼如下:
// Application.java
@SpringBootApplication
public class Application {
}
差異點,在於無需排除 ElasticsearchAutoConfiguration 和 ElasticsearchDataAutoConfiguration 自動配置類,因為需要它們自動化配置 Spring Data Elasticsearch 。3.3 配置文件在 application.yml 中,添加 Jest 配置,如下:
spring:
data:
# Elasticsearch 配置項
elasticsearch:
cluster-name: elasticsearch # 集群名
cluster-nodes: 127.0.0.1:9300 # 集群節點
差異點,將配置項 jest 替換成 elasticsearch 。我們使用本地的 ES 服務。默認情況下,ES transport 連接方式暴露的埠是 9300 。3.4 ESProductDO和 「2.4 ESProductDO」 一致。
3.5 ProductRepository和 「3.5 ProductRepository」 一致。
3.6 簡單測試和 「3.6 簡單測試」 一致。
😈 有沒感受到,Spring Data Jest 和 Spring Data Elasticsearch 是基本一致的。
4. 基於方法名查詢示例代碼對應倉庫:lab-15-spring-data-jest 。
spring-boot-starter-data-jest :3.2.5.RELEASE在 《芋道 Spring Boot JPA 入門》 文章的「4. 基於方法名查詢」小節中,我們已經提到:
在 Spring Data 中,支持根據方法名作生成對應的查詢(WHERE)條件,進一步進化我們使用 JPA ,具體是方法名以 findBy、existsBy、countBy、deleteBy 開頭,後面跟具體的條件。具體的規則,在 《Spring Data JPA —— Query Creation》 文檔中,已經詳細提供。如下:
關鍵字方法示例JPQL snippetAndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2LessThanfindByAgeLessThan… where x.age < ?1LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1GreaterThanfindByAgeGreaterThan… where x.age > ?1GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1AfterfindByStartDateAfter… where x.startDate > ?1BeforefindByStartDateBefore… where x.startDate < ?1IsNull, NullfindByAge(Is)Null… where x.age is nullIsNotNull, NotNullfindByAge(Is)NotNull… where x.age not nullLikefindByFirstnameLike… where x.firstname like ?1NotLikefindByFirstnameNotLike… where x.firstname not like ?1StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname descNotfindByLastnameNot… where x.lastname <> ?1InfindByAgeIn(Collection ages)… where x.age in ?1NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1TruefindByActiveTrue()… where x.active = trueFalsefindByActiveFalse()… where x.active = falseIgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)注意,如果我們有排序需求,可以使用 OrderBy 關鍵字。下面,我們來編寫一個簡單的示例。
艿艿:IDEA 牛逼,提供的插件已經能夠自動提示上述關鍵字。太強了~
因為 Spring Data Elasticsearch 和 Spring Data Jest 也是 Spring Data 體系中的一員,所以也能享受到基於方法名查詢的福利。所以,我們在本小節中,我們也來嘗試下。
我們會在 「2. Spring Data Jest」 的示例代碼對應倉庫 lab-15-spring-data-jest 的基礎上,進行本小節的示例。
4.1 ProductRepository02在 cn.iocoder.springboot.lab15.springdatajest.repository 包路徑下,創建 UserRepository02 接口。代碼如下:
// ProductRepository02.java
public interface ProductRepository02 extends ElasticsearchRepository<ESProductDO, Integer> {
ESProductDO findByName(String name);
Page<ESProductDO> findByNameLike(String name, Pageable pageable);
}
對於分頁操作,需要使用到 Pageable 參數,需要作為方法的最後一個參數。4.2 簡單測試創建 UserRepository02Test 測試類,我們來測試一下簡單的 UserRepository02Test 的每個操作。代碼如下:
// UserRepository02Test.java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepository02Test {
@Autowired
private ProductRepository02 productRepository;
@Test // 根據名字獲得一條記錄
public void testFindByName() {
ESProductDO product = productRepository.findByName("芋道源碼");
System.out.println(product);
}
@Test // 使用 name 模糊查詢,分頁返回結果
public void testFindByNameLike() {
// 根據情況,是否要製造測試數據
if (true) {
testInsert();
}
// 創建排序條件
Sort sort = new Sort(Sort.Direction.DESC, "id"); // ID 倒序
// 創建分頁條件。
Pageable pageable = PageRequest.of(0, 10, sort);
// 執行分頁操作
Page<ESProductDO> page = productRepository.findByNameLike("芋道", pageable);
// 列印
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
}
/**
* 為了給分頁製造一點數據
*/
private void testInsert() {
for (int i = 1; i <= 100; i++) {
ESProductDO product = new ESProductDO();
product.setId(i); // 一般 ES 的 ID 編號,使用 DB 數據對應的編號。這裡,先寫死
product.setName("芋道源碼:" + i);
product.setSellPoint("願半生編碼,如一生老友");
product.setDescription("我只是一個描述");
product.setCid(1);
product.setCategoryName("技術");
productRepository.save(product);
}
}
}具體的,胖友可以自己跑跑,妥妥的。
5. 複雜查詢在一些業務場景下,我們需要編寫相對複雜的查詢,例如說類似京東 https://search.jd.com/Search?keyword=華為手機 搜索功能,需要支持關鍵字、分類、品牌等等,並且可以按照綜合、銷量等等升降序排序,那麼我們就無法在上面看到的 Spring Data Repository 提供的簡單的查詢方法,而需要使用到 ElasticsearchRepository 的 search 方法,代碼如下:
// ElasticsearchRepository.java
// 省略非 search 方法
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);此時,我們就需要使用 QueryBuilder 和 SearchQuery 構建相對複雜的搜索和排序條件。所以,我們繼續在 「2. Spring Data Jest」 的示例代碼對應倉庫 lab-15-spring-data-jest 的基礎上,進行本小節的示例,實現一個簡單的商品搜索功能。
5.1 ProductRepository03在 cn.iocoder.springboot.lab15.springdatajest.repository 包路徑下,創建 ProductRepository03 接口。代碼如下:
// ProductRepository03.java
public interface ProductRepository03 extends ElasticsearchRepository<ESProductDO, Integer> {
default Page<ESProductDO> search(Integer cid, String keyword, Pageable pageable) {
// <1> 創建 NativeSearchQueryBuilder 對象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// <2.1> 篩選條件 cid
if (cid != null) {
nativeSearchQueryBuilder.withFilter(QueryBuilders.termQuery("cid", cid));
}
// <2.2> 篩選
if (StringUtils.hasText(keyword)) {
FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = { // TODO 芋艿,分值隨便打的
new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("name", keyword),
ScoreFunctionBuilders.weightFactorFunction(10)),
new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("sellPoint", keyword),
ScoreFunctionBuilders.weightFactorFunction(2)),
new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("categoryName", keyword),
ScoreFunctionBuilders.weightFactorFunction(3)),
// new FunctionScoreQueryBuilder.FilterFunctionBuilder(matchQuery("description", keyword),
// ScoreFunctionBuilders.weightFactorFunction(2)), // TODO 芋艿,目前這麼做,如果商品描述很長,在按照價格降序,會命中超級多的關鍵字。
};
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(functions)
.scoreMode(FunctionScoreQuery.ScoreMode.SUM) // 求和
.setMinScore(2F); // TODO 芋艿,需要考慮下 score
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
}
// 排序
if (StringUtils.hasText(keyword)) { // <3.1> 關鍵字,使用打分
nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
} else if (pageable.getSort().isSorted()) { // <3.2> 有排序,則進行拼接
pageable.getSort().get().forEach(sortField -> nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField.getProperty())
.order(sortField.getDirection().isAscending() ? SortOrder.ASC : SortOrder.DESC)));
} else { // <3.3> 無排序,則按照 ID 倒序
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
}
// <4> 分頁
nativeSearchQueryBuilder.withPageable(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); // 避免
// <5> 執行查詢
return search(nativeSearchQueryBuilder.build());
}
}
使用 QueryBuilder 和 SearchQuery 構建相對複雜的搜索和排序條件,我們可以放在 Service 層,也可以放在 Repository 層。艿艿個人的偏好放在 Repository 層。主要原因是,儘量避免數據層的操作暴露在 Service 層。缺點呢,就像我們這裡看到的,有點業務邏輯就到了 Repository 層。😈 有舍有得,看個人喜好。翻了一些開源項目,放在 Service 或 Repository 層的都有。簡單來說下這個方法的整體邏輯,根據商品分類編號 + 關鍵字,檢索相應的商品,分頁返回結果。<1> 處,創建 NativeSearchQueryBuilder 對象。<2.1> 處,如果有分類編號 cid ,則進行篩選。<2.2> 處,如果有關鍵字 keyword ,則按照 name 10 分、sellPoint 2 分、categoryName 3 分,計算求和,篩選至少滿足 2 分。<3.1> 處,如果有關鍵字,則按照打分結果降序。<3.2> 處,如果有排序條件,則按照該排序即可。<3.3> 處,如果無排序條件,則按照 ID 編號降序。<4> 處,創建新的 PageRequest 對象,避免 pageable 裡原有的排序條件。<5> 處,調用 #search(SearchQuery searchQuery) 方法,執行 Elasticsearch 搜索。5.2 ProductRepository03Test創建 ProductRepository03Test 測試類,我們來測試一下簡單的 UserRepository03Test 的每個操作。代碼如下:
// ProductRepository03Test.java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepository03Test {
@Autowired
private ProductRepository03 productRepository;
@Test
public void testSearch() {
// 查找分類為 1 + 指定關鍵字,並且按照 id 升序
Page<ESProductDO> page = productRepository.search(1, "技術",
PageRequest.of(0, 5, Sort.Direction.ASC, "id"));
System.out.println(page.getTotalPages());
// 查找分類為 1 ,並且按照 id 升序
page = productRepository.search(1, null,
PageRequest.of(0, 5, Sort.Direction.ASC, "id"));
System.out.println(page.getTotalPages());
}
}具體的,胖友可以自己跑跑,妥妥的。
6. ElasticsearchTemplate在 Spring Data Elasticsearch 中,有一個 ElasticsearchTemplate 類,提供了 Elasticsearch 操作模板,方便我們操作 Elasticsearch 。
😈 要注意,這是 Spring Data Elasticsearch 獨有,而 Spring Data Jest 沒有的一個類。
咳咳咳,當艿艿寫完這篇博客後,突然發現,Spring Data Jest 有一個 JestElasticsearchTemplate 類,和 ElasticsearchTemplate 是對等的。
也因此,我們繼續在 「3. Spring Data Elasticsearch」 的示例代碼對應倉庫 lab-15-spring-data-elasticsearch 的基礎上,進行本小節的示例,實現一個商品搜索條件返回的功能。
6.1 ProductConditionBO在 cn.iocoder.springboot.lab15.springdataelasticsearch.bo 包路徑下,創建 ProductConditionBO 類,商品搜索條件 BO ,代碼如下:
// ProductConditionBO.java
public class ProductConditionBO {
/**
* 商品分類數組
*/
private List<Category> categories;
public static class Category {
/**
* 分類編號
*/
private Integer id;
/**
* 分類名稱
*/
private String name;
// ... 省略 setting/getting 方法
}
// ... 省略 setting/getting 方法
}
6.2 簡單示例創建 ProductRepository04Test 測試類,我們來測試一下簡單的 ProductRepository04Test 的每個操作。代碼如下:
// ProductRepository04Test.java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ProductRepository04Test {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void test() {
// <1> 創建 ES 搜索條件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
.withIndices("product");;
// <2> 篩選
nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery("芋道",
"name", "sellPoint", "categoryName"));
// <3> 聚合
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("cids").field("cid")); // 商品分類
// <4> 執行查詢
ProductConditionBO condition = elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), response -> {
ProductConditionBO result = new ProductConditionBO();
// categoryIds 聚合
Aggregation categoryIdsAggregation = response.getAggregations().get("cids");
if (categoryIdsAggregation != null) {
result.setCategories(new ArrayList<>());
for (LongTerms.Bucket bucket : (((LongTerms) categoryIdsAggregation).getBuckets())) {
result.getCategories().add(new ProductConditionBO.Category().setId(bucket.getKeyAsNumber().intValue()));
}
}
// 返回結果
return result;
});
// <5> 後續遍歷 condition.categories 數組,查詢商品分類,設置商品分類名。
System.out.println();
}
}
簡單來說下這個方法的整體邏輯,根據關鍵字檢索 name、sellPoint、categoryName 欄位,聚合 cid 返回。<1> 處,創建 NativeSearchQueryBuilder 對象,並設置查詢的索引是 product ,即 ESProductDO 類的對應的索引。<2> 處,根據關鍵字檢索 name、sellPoint、categoryName 欄位。此處,我們使用的關鍵字是 "芋道" 。<3> 處,將商品分類編號 cid 聚合成 cids 返回。如果 ESProductDO 上有品牌編號,我們可以多在聚合一個品牌編號返回。<4> 處,執行查詢,解析聚合結果,設置回 ProductConditionBO 中。<5> 處,後續遍歷 condition.categories 數組,查詢商品分類,設置商品分類名。6.3 小結可能胖友會有疑惑?Spring Data Jest 沒有 ElasticsearchTemplate 類,豈不是不能實現當前示例麼?答案是否定的,我們回過頭看 「5. 複雜查詢」 。對於 Spring Data Jest 來說,可以通過 ElasticsearchRepository 提供的 search 方法,實現聚合操作的功能。當然,Spring Data Elasticsearch 也可以。
所以呢,絕大多數情況下,我們並不會直接使用 ElasticsearchTemplate 類。
666. 彩蛋通過寫這篇文章,艿艿自己也查了一些資料,終於把 Elasticsearch 客戶端的情況理順了。😈 當然,也推薦幾篇艿艿覺得不錯的 Elasticsearch 文章:
《全文搜尋引擎選 ElasticSearch 還是 Solr?》《別再說你不會 ElasticSearch 調優了,都給你整理好了》《Elasticsearch 如何做到億級數據查詢毫秒級返回?》《日均 5 億查詢量的京東訂單中心,為什麼舍 MySQL 用 ES ?》另外,在推薦一個 Chrome ElasticSearch Head 插件,可用於監控 Elasticsearch 狀態的客戶端,提供數據可視化、執行增刪改查操作等等功能。