在 Elasticsearch 中使用 TermQueries
1. 引言
TermQuery是 Elasticsearch 的核心建置模組之一。它無需分析、分詞或文字轉換即可對欄位執行精確匹配。因此,它非常適合處理結構化數據,例如:
- 關鍵字(標籤、類別、角色)
- 布林值
- 數值
- 關鍵字(標籤、類別、角色)
識別碼(UUID、SKU、使用者 ID)
在本文中,我們將重點介紹一個常見的實際場景,即在布林過濾器中使用多個術語查詢來建立搜尋條件。
2. 使用 Elasticsearch 查詢 DSL 進行詞項查詢
我們將從使用查詢 DSL直接查詢 Elasticsearch 開始。首先,我們將建立users索引:
PUT /users
{
"mappings": {
"properties": {
"id": { "type": "keyword" },
"name": { "type": "text" },
"role": { "type": "keyword" },
"is_active": { "type": "boolean" }
}
}
}
這裡,我們為使用者定義了多個欄位。我們將使用role和is_active欄位進行詞項查詢。接下來,我們將在索引中新增多個使用者:
POST /users/_bulk
{ "index": { "_id": "1" } }
{ "id": "1", "name": "Alice", "role": "admin", "is_active": true }
{ "index": { "_id": "2" } }
{ "id": "2", "name": "Bob", "role": "user", "is_active": true }
{ "index": { "_id": "3" } }
{ "id": "3", "name": "Charlie", "role": "admin", "is_active": false }
{ "index": { "_id": "4" } }
{ "id": "4", "name": "Diana", "role": "manager", "is_active": true }
最後,我們將執行一個帶有多個詞項過濾器的查詢:
GET /users/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "role": "admin" } },
{ "term": { "is_active": true } }
]
}
}
}
我們使用Boolean指令,透過AND條件將多個詞項過濾器組合在一起。在回應中,我們可以看到只有一個使用者符合過濾器要求:
"hits": [
{
"_index": "users",
"_id": "1",
"_score": 0.0,
"_source": {
"id": "1",
"name": "Alice",
"role": "admin",
"is_active": true
}
}
]
透過這種組合,我們可以獲得快速且對快取友好的搜尋結果。但是,我們會失去評分功能,並且依賴精確匹配。
3. 依賴關係和配置
要設定 Elasticsearch Java 用戶端和 Spring Data Elasticsearch 儲存庫,我們首先新增spring-data-elasticsearch依賴項:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>${spring-data-elasticsearch.version}</version>
</dependency>
接下來,讓我們將 Elasticsearch 主機和連接埠資訊新增至application.yml檔案:
elasticsearch:
hostAndPort: localhost:9200
現在,我們準備好建立包含所有必要客戶端的ElasticsearchConfiguration :
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.baeldung.spring.data.es.termsqueries.repository")
public class ElasticsearchConfiguration extends AbstractElasticsearchConfiguration {
@Value("${elasticsearch.hostAndPort}")
private String hostAndPort;
@Bean
@Override
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(hostAndPort)
.build();
return RestClients.create(clientConfiguration).rest();
}
@Bean
public ElasticsearchClient elasticsearchLowLevelClient() {
RestClient restClient = RestClient.builder(HttpHost.create("http://" + hostAndPort))
.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
這裡,我們建立了一個RestHighLevelClient bean,用於 Spring Data 儲存庫。我們也定義了一個ElasticsearchClient bean,用於底層互動。**此外,我們也指定了 Spring Data 儲存庫的套件。**在這兩個 bean 中,我們都使用了屬性檔案中指定的 Elasticsearch 主機和連接埠。
4. 使用 Elasticsearch Java 用戶端進行詞項查詢
現在,讓我們使用ElasticsearchLowLevelClient bean 準備一個傳送到 Elasticsearch 的詞項查詢:
@SpringBootTest
@ContextConfiguration(classes = ElasticsearchConfiguration.class)
public class ElasticSearchTermsQueriesManualTest {
@Autowired
private ElasticsearchClient elasticsearchLowLevelClient;
@Test
void givenAdminRoleAndActiveStatusFilter_whenSearch_thenReturnsOnlyActiveAdmins() throws Exception {
Query roleQuery = TermQuery.of(t -> t.field("role.keyword").value("admin"))._toQuery();
Query activeQuery = TermQuery.of(t -> t.field("is_active").value(true))._toQuery();
Query boolQuery = BoolQuery.of(b -> b.filter(roleQuery).filter(activeQuery))._toQuery();
SearchRequest request = SearchRequest.of(s -> s.index("users").query(boolQuery));
SearchResponse<Map> response = elasticsearchLowLevelClient.search(request, Map.class);
assertThat(response.hits().hits())
.hasSize(1)
.first()
.extracting(Hit::source)
.satisfies(source -> {
assertThat(source)
.isNotNull()
.values()
.containsExactly("1", "Alice", "admin", true);
});
}
}
我們使用TermQuery來取得角色和 is_active 欄位。然後,我們將它們與BoolQuery和filter結合使用。我們將響應映射到Map類別。如預期的那樣,我們從使用者索引中只檢索到一個匹配項。
5. 使用 Spring Data Elasticsearch 進行詞項查詢
我們可以使用 Spring Data 儲存庫查詢索引。透過正確的映射,我們將獲得相同的詞項查詢行為。讓我們建立一個User模型:
@Document(indexName = "users")
public class User {
@Id
private String id;
@Field(type = FieldType.Keyword, name = "role")
private String role;
@Field(type = FieldType.Text, name = "name")
private String name;
@Field(type = FieldType.Boolean, name = "is_active")
private Boolean isActive;
// Getters and setters
}
我們已指定索引名稱以及所有欄位名稱和類型,以確保正確的對應。現在,讓我們建立UserRepository :
public interface UserRepository extends ElasticsearchRepository<User, String> {
List<User> findByRoleAndIsActive(String role, boolean isActive);
}
我們擴展了ElasticsearchRepository ,並添加了findByRoleAndIsActive方法,用於按role和isActive搜尋使用者。最後,讓我們呼叫我們的儲存庫:
@SpringBootTest
@ContextConfiguration(classes = ElasticsearchConfiguration.class)
public class ElasticSearchTermsQueriesManualTest {
@Autowired
private UserRepository userRepository;
@Test
void givenAdminRoleAndActiveStatusFilter_whenSearchUsingRepository_thenReturnsOnlyActiveAdmins() throws Exception {
List<User> users = userRepository.findByRoleAndIsActive("admin", true);
assertThat(users)
.hasSize(1)
.first()
.satisfies(user -> {
assertThat(user.getId()).isEqualTo("1");
assertThat(user.getName()).isEqualTo("Alice");
assertThat(user.getRole()).isEqualTo("admin");
assertThat(user.getIsActive()).isTrue();
});
}
}
正如預期的那樣,我們只檢索到一個使用者。實際上,Spring Data 儲存庫建構的是相同的詞項查詢。雖然我們失去了靈活性和控制權,但我們得到了一個無需任何實作細節的簡單替代方案。
6. 聚合內部的詞項查詢
我們可以將詞項查詢與聚合結合起來,從篩選後的資料中獲得分析見解。例如,讓我們統計每個角色下的使用者數量,但僅統計活躍使用者:
@SpringBootTest
@ContextConfiguration(classes = ElasticsearchConfiguration.class)
public class ElasticSearchTermsQueriesManualTest {
@Autowired
private ElasticsearchClient elasticsearchLowLevelClient;
@Test
void givenActiveUsers_whenAggregateByRole_thenReturnsRoleCounts() throws Exception {
Query activeQuery = TermQuery.of(t -> t.field("is_active").value(true))._toQuery();
Aggregation aggregation = Aggregation.of(a -> a
.terms(t -> t.field("role.keyword")));
SearchRequest request = SearchRequest.of(s -> s
.index("users")
.query(activeQuery)
.aggregations("by_role", aggregation));
SearchResponse<Void> response = elasticsearchLowLevelClient.search(request, Void.class);
StringTermsAggregate rolesAggregate = response.aggregations().get("by_role").sterms();
assertThat(rolesAggregate.buckets().array())
.extracting(b -> b.key().stringValue())
.containsExactlyInAnyOrder("admin", "user", "manager");
assertThat(rolesAggregate.buckets().array())
.extracting(MultiBucketBase::docCount)
.contains(1L, 1L, 1L);
}
}
我們首先使用基於is_active欄位的詞項查詢來篩選活躍用戶。然後,我們使用詞項聚合,根據role.keyword欄位對這些使用者進行聚合。這種方法有效率地結合了篩選和聚合,因為兩者都依賴相同的倒排索引查找。 Elasticsearch 無需掃描所有文件;它只需統計篩選子集中匹配的詞項即可。
7. 結論
本文回顧了 Elasticsearch 的詞項查詢。我們探討了使用它們的不同方法,從直接 DSL 呼叫到 Spring Data 儲存庫。我們還回顧了它們的聚合功能,以便有效地分析過濾後的數據。考慮到它們的局限性,我們可以調整方法,以實現與 Elasticsearch 索引的快速而優雅的整合。
和往常一樣,程式碼可以在 GitHub 上找到。