From 8c90c744834b84dd25ced0830236e96884f047c5 Mon Sep 17 00:00:00 2001 From: Dmitry Zinkevich Date: Tue, 23 Feb 2016 12:44:13 +0300 Subject: [PATCH] Add test cases with different query types --- .../spring/data/es/config/Config.java | 20 +- .../spring/data/es/dao/ArticleRepository.java | 15 -- .../spring/data/es/model/Article.java | 33 ++- .../data/es/ElasticSearchQueryTest.java | 211 ++++++++++++++++++ 4 files changed, 248 insertions(+), 31 deletions(-) delete mode 100644 spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/dao/ArticleRepository.java create mode 100644 spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java index 78e4083a28..3857056b70 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java @@ -1,5 +1,6 @@ package com.baeldung.spring.data.es.config; +import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.node.NodeBuilder; import org.slf4j.Logger; @@ -17,20 +18,14 @@ import java.nio.file.Path; import java.nio.file.Paths; @Configuration -@EnableElasticsearchRepositories(basePackages = "com.baeldung.repository") +@EnableElasticsearchRepositories(basePackages = "com.baeldung.spring.data.es.repository") @ComponentScan(basePackages = {"com.baeldung.spring.data.es.service"}) public class Config { private static Logger logger = LoggerFactory.getLogger(Config.class); @Bean - public NodeBuilder nodeBuilder() { - return new NodeBuilder(); - } - - @Bean - public ElasticsearchOperations elasticsearchTemplate() { - + public Client client() { try { Path tmpDir = Files.createTempDirectory(Paths.get(System.getProperty("java.io.tmpdir")), "elasticsearch_data"); @@ -40,14 +35,19 @@ public class Config { logger.debug(tmpDir.toAbsolutePath().toString()); - return new ElasticsearchTemplate(nodeBuilder() + return new NodeBuilder() .local(true) .settings(elasticsearchSettings.build()) .node() - .client()); + .client(); } catch (IOException ioex) { logger.error("Cannot create temp dir", ioex); throw new RuntimeException(); } } + + @Bean + public ElasticsearchOperations elasticsearchTemplate() { + return new ElasticsearchTemplate(client()); + } } diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/dao/ArticleRepository.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/dao/ArticleRepository.java deleted file mode 100644 index 313eba5b36..0000000000 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/dao/ArticleRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.baeldung.spring.data.es.dao; - -import com.baeldung.spring.data.es.model.Article; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.annotations.Query; -import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; - -public interface ArticleRepository extends ElasticsearchRepository { - - Page
findByAuthorsName(String name, Pageable pageable); - - @Query("{\"bool\": {\"must\": [{\"match\": {\"authors.name\": \"?0\"}}]}}") - Page
findByAuthorsNameUsingCustomQuery(String name, Pageable pageable); -} diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java index dd472982ce..40db51ac13 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java @@ -1,23 +1,35 @@ package com.baeldung.spring.data.es.model; 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.FieldIndex; -import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.*; +import java.util.Arrays; import java.util.List; +import static org.springframework.data.elasticsearch.annotations.FieldIndex.not_analyzed; +import static org.springframework.data.elasticsearch.annotations.FieldType.Nested; +import static org.springframework.data.elasticsearch.annotations.FieldType.String; + @Document(indexName = "blog", type = "article") public class Article { @Id private String id; - @Field(type = FieldType.String, index = FieldIndex.not_analyzed) + + @MultiField( + mainField = @Field(type = String), + otherFields = { + @NestedField(index = not_analyzed, dotSuffix = "verbatim", type = String) + } + ) private String title; - @Field(type = FieldType.Nested) + + @Field(type = Nested) private List authors; + @Field(type = String, index = not_analyzed) + private String[] tags; + public Article() { } @@ -49,12 +61,21 @@ public class Article { this.authors = authors; } + public String[] getTags() { + return tags; + } + + public void setTags(String... tags) { + this.tags = tags; + } + @Override public String toString() { return "Article{" + "id='" + id + '\'' + ", title='" + title + '\'' + ", authors=" + authors + + ", tags=" + Arrays.toString(tags) + '}'; } } diff --git a/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java b/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java new file mode 100644 index 0000000000..fbc18cbb4c --- /dev/null +++ b/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java @@ -0,0 +1,211 @@ +package com.baeldung.spring.data.es; + +import com.baeldung.spring.data.es.config.Config; +import com.baeldung.spring.data.es.model.Article; +import com.baeldung.spring.data.es.model.Author; +import com.baeldung.spring.data.es.service.ArticleService; +import org.elasticsearch.action.ActionFuture; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.unit.Fuzziness; +import org.elasticsearch.index.query.MultiMatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.SearchQuery; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.elasticsearch.index.query.MatchQueryBuilder.Operator.AND; +import static org.elasticsearch.index.query.QueryBuilders.*; +import static org.junit.Assert.assertEquals; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {Config.class}, loader = AnnotationConfigContextLoader.class) +public class ElasticSearchQueryTest { + + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; + + @Autowired + private ArticleService articleService; + + @Autowired + private Client client; + + private final Author johnSmith = new Author("John Smith"); + private final Author johnDoe = new Author("John Doe"); + + @Before + public void before() { + elasticsearchTemplate.deleteIndex(Article.class); + elasticsearchTemplate.createIndex(Article.class); + elasticsearchTemplate.putMapping(Article.class); + elasticsearchTemplate.refresh(Article.class, true); + + Article article = new Article("Spring Data Elasticsearch"); + article.setAuthors(asList(johnSmith, johnDoe)); + article.setTags("elasticsearch", "spring data"); + articleService.save(article); + + article = new Article("Search engines"); + article.setAuthors(asList(johnDoe)); + article.setTags("search engines", "tutorial"); + articleService.save(article); + + article = new Article("Second Article About Elasticsearch"); + article.setAuthors(asList(johnSmith)); + article.setTags("elasticsearch", "spring data"); + articleService.save(article); + + article = new Article("Elasticsearch Tutorial"); + article.setAuthors(asList(johnDoe)); + article.setTags("elasticsearch"); + articleService.save(article); + } + + @Test + public void givenFullTitle_whenRunMatchQuery_thenDocIsFound() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchQuery("title", "Search engines").operator(AND)) + .build(); + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(1, articles.size()); + } + + @Test + public void givenOneTermFromTitle_whenRunMatchQuery_thenDocIsFound() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchQuery("title", "Engines Solutions")) + .build(); + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(1, articles.size()); + assertEquals("Search engines", articles.get(0).getTitle()); + } + + @Test + public void givenPartTitle_whenRunMatchQuery_thenDocIsFound() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchQuery("title", "elasticsearch data")) + .build(); + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(3, articles.size()); + } + + @Test + public void givenFullTitle_whenRunMatchQueryOnVerbatimField_thenDocIsFound() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch")) + .build(); + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(1, articles.size()); + + searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchQuery("title.verbatim", "Second Article About")) + .build(); + articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(0, articles.size()); + } + + @Test + public void givenNestedObject_whenQueryByAuthorsName_thenFoundArticlesByThatAuthor() { + QueryBuilder builder = nestedQuery("authors", + boolQuery().must(termQuery("authors.name", "smith"))); + + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build(); + List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + + assertEquals(2, articles.size()); + } + + @Test + public void givenAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTokenCountsSeparately() { + TermsBuilder aggregation = AggregationBuilders.terms("top_tags").field("title"); + SearchResponse response = client.prepareSearch("blog").setTypes("article") + .addAggregation(aggregation).execute().actionGet(); + + Map results = response.getAggregations().asMap(); + StringTerms topTags = (StringTerms) results.get("top_tags"); + + List keys = topTags.getBuckets().stream().map(b -> b.getKey()).collect(toList()); + Collections.sort(keys); + assertEquals(asList("about", "article", "data", "elasticsearch", + "engines", "search", "second", "spring", "tutorial"), keys); + } + + @Test + public void givenNotAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTermCountsIndividually() { + TermsBuilder aggregation = AggregationBuilders.terms("top_tags").field("tags") + .order(Terms.Order.aggregation("_count", false)); + SearchResponse response = client.prepareSearch("blog").setTypes("article") + .addAggregation(aggregation).execute().actionGet(); + + Map results = response.getAggregations().asMap(); + StringTerms topTags = (StringTerms) results.get("top_tags"); + + List keys = topTags.getBuckets().stream().map(b -> b.getKey()).collect(toList()); + assertEquals(asList("elasticsearch", "spring data", "search engines", "tutorial"), keys); + } + + @Test + public void givenNotExactPhrase_whenUseSlop_thenQueryMatches() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1)) + .build(); + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(1, articles.size()); + } + + @Test + public void givenPhraseWithType_whenUseFuzziness_thenQueryMatches() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(matchQuery("title", "spring date elasticserch") + .operator(AND) + .fuzziness(Fuzziness.ONE) + .prefixLength(3)) + .build(); + + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(1, articles.size()); + } + + @Test + public void givenMultimatchQuery_whenDoSearch_thenAllProvidedFieldsMatch() { + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(multiMatchQuery("tutorial") + .field("title") + .field("tags") + .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)) + .build(); + + List
articles = elasticsearchTemplate + .queryForList(searchQuery, Article.class); + assertEquals(2, articles.size()); + } +}