From 4bff4d65af947bff6bfbbf8b6d772c1621508451 Mon Sep 17 00:00:00 2001 From: "ICKostiantyn.Ivanov" Date: Thu, 13 Nov 2025 19:09:05 +0100 Subject: [PATCH] BAEL-9121 - Using TermQueries in Elastic Search --- .../config/ElasticsearchConfiguration.java | 41 +++++++ .../data/es/termsqueries/model/User.java | 20 ++++ .../repository/UserRepository.java | 10 ++ .../ElasticSearchTermsQueriesManualTest.java | 104 ++++++++++++++++++ .../src/test/resources/application.yml | 2 + 5 files changed, 177 insertions(+) create mode 100644 persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/config/ElasticsearchConfiguration.java create mode 100644 persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/model/User.java create mode 100644 persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/repository/UserRepository.java create mode 100644 persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/termsqueries/ElasticSearchTermsQueriesManualTest.java create mode 100644 persistence-modules/spring-data-elasticsearch/src/test/resources/application.yml diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/config/ElasticsearchConfiguration.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/config/ElasticsearchConfiguration.java new file mode 100644 index 000000000000..fa4d7413bd7e --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/config/ElasticsearchConfiguration.java @@ -0,0 +1,41 @@ +package com.baeldung.spring.data.es.termsqueries.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestHighLevelClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.erhlc.AbstractElasticsearchConfiguration; +import org.springframework.data.elasticsearch.client.erhlc.RestClients; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; + +@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); + } +} diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/model/User.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/model/User.java new file mode 100644 index 000000000000..ab42fce3fee4 --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/model/User.java @@ -0,0 +1,20 @@ +package com.baeldung.spring.data.es.termsqueries.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; + +@Data +@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; +} diff --git a/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/repository/UserRepository.java b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/repository/UserRepository.java new file mode 100644 index 000000000000..780bf1bdfc11 --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/termsqueries/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.spring.data.es.termsqueries.repository; + +import com.baeldung.spring.data.es.termsqueries.model.User; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.List; + +public interface UserRepository extends ElasticsearchRepository { + List findByRoleAndIsActive(String role, boolean isActive); +} diff --git a/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/termsqueries/ElasticSearchTermsQueriesManualTest.java b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/termsqueries/ElasticSearchTermsQueriesManualTest.java new file mode 100644 index 000000000000..ef32f87fa81c --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/termsqueries/ElasticSearchTermsQueriesManualTest.java @@ -0,0 +1,104 @@ +package com.baeldung.spring.data.es.termsqueries; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; +import co.elastic.clients.elasticsearch._types.aggregations.MultiBucketBase; +import co.elastic.clients.elasticsearch._types.aggregations.StringTermsAggregate; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery; +import co.elastic.clients.elasticsearch.core.SearchRequest; +import co.elastic.clients.elasticsearch.core.SearchResponse; +import co.elastic.clients.elasticsearch.core.search.Hit; +import com.baeldung.spring.data.es.termsqueries.config.ElasticsearchConfiguration; +import com.baeldung.spring.data.es.termsqueries.model.User; +import com.baeldung.spring.data.es.termsqueries.repository.UserRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This Manual test requires: Elasticsearch instance running on localhost:9200. + *

+ * The following docker command can be used: + * docker run -d --name elastic-test -p 9200:9200 -e "discovery.type=single-node" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:8.9.0 + */ +@Slf4j +@SpringBootTest +@ContextConfiguration(classes = ElasticsearchConfiguration.class) +public class ElasticSearchTermsQueriesManualTest { + + @Autowired + private ElasticsearchClient elasticsearchLowLevelClient; + + @Autowired + private UserRepository userRepository; + + + @Test + void givenAdminRoleAndActiveStatusFilter_whenSearch_thenReturnsOnlyActiveAdmins() throws Exception { + Query roleQuery = TermQuery.of(t -> t.field("role").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 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); + }); + } + + @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 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); + } + + @Test + void givenAdminRoleAndActiveStatusFilter_whenSearchUsingRepository_thenReturnsOnlyActiveAdmins() throws Exception { + List 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(); + }); + } +} diff --git a/persistence-modules/spring-data-elasticsearch/src/test/resources/application.yml b/persistence-modules/spring-data-elasticsearch/src/test/resources/application.yml new file mode 100644 index 000000000000..900ae1f9b635 --- /dev/null +++ b/persistence-modules/spring-data-elasticsearch/src/test/resources/application.yml @@ -0,0 +1,2 @@ +elasticsearch: + hostAndPort: localhost:9200 \ No newline at end of file