From 9cffcef9c6556a8c3846044130e6b00a5a38e3f9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 21 Oct 2014 08:33:25 +0200 Subject: [PATCH] #57 - Added samples for Solr highlighting, boost, score, functions and realtime-get. Refactored initialization to allow custom ingestion of test data. Added samples demonstrating usage of: - @Highlight for highlighting fragments. - @Boost for boosting search terms. - @Score to indicate interest in document score. - SolrOperations#getById for realtime-get. --- .../java/example/springdata/solr/Product.java | 10 +- .../springdata/solr/ProductRepository.java | 27 +++ .../solr/ProductRepositoryCustom.java | 6 + .../solr/AdvancedSolrRepositoryTests.java | 189 ++++++++++++++++++ ...sts.java => BasicSolrRepositoryTests.java} | 21 +- .../solr/SolrTestConfiguration.java | 8 +- 6 files changed, 240 insertions(+), 21 deletions(-) create mode 100644 solr/example/src/test/java/example/springdata/solr/AdvancedSolrRepositoryTests.java rename solr/example/src/test/java/example/springdata/solr/{SolrRepositoryTests.java => BasicSolrRepositoryTests.java} (79%) diff --git a/solr/example/src/main/java/example/springdata/solr/Product.java b/solr/example/src/main/java/example/springdata/solr/Product.java index bdebed2e..fb385032 100644 --- a/solr/example/src/main/java/example/springdata/solr/Product.java +++ b/solr/example/src/main/java/example/springdata/solr/Product.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,10 @@ import org.springframework.data.annotation.Id; import org.springframework.data.geo.Point; import org.springframework.data.solr.core.mapping.Indexed; import org.springframework.data.solr.core.mapping.SolrDocument; +import org.springframework.data.solr.repository.Score; /** - * Document representing a Product and its attributes matching the fieldes defined in the example solr schema. * * @author Christoph Strobl @@ -38,11 +39,14 @@ public class Product { private @Indexed String name; private @Indexed(name = "cat") List category; private @Indexed(name = "store") Point location; + private @Indexed String description; private @Indexed boolean inStock; + private @Indexed Integer popularity; + private @Score Float score; @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", category=" + category + ", location=" + location + ", inStock=" - + inStock + "]"; + + inStock + ", score=" + score + "]"; } } diff --git a/solr/example/src/main/java/example/springdata/solr/ProductRepository.java b/solr/example/src/main/java/example/springdata/solr/ProductRepository.java index b5e9b46e..fdcbd7e9 100644 --- a/solr/example/src/main/java/example/springdata/solr/ProductRepository.java +++ b/solr/example/src/main/java/example/springdata/solr/ProductRepository.java @@ -15,7 +15,14 @@ */ package example.springdata.solr; +import java.util.List; + +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.solr.core.query.result.HighlightPage; +import org.springframework.data.solr.repository.Boost; +import org.springframework.data.solr.repository.Highlight; +import org.springframework.data.solr.repository.Query; /** * Repository definition for {@link Product}. @@ -24,4 +31,24 @@ import org.springframework.data.repository.CrudRepository; */ public interface ProductRepository extends ProductRepositoryCustom, CrudRepository { + /** + * Find documents with matching description, highlighting context within a 20 char range around the hit. + * + * @param description + * @param page + * @return + */ + @Highlight(fragsize = 20, snipplets = 3) + HighlightPage findByDescriptionStartingWith(String description, Pageable page); + + /** + * Find the first 10 documents with a match in name or description. Boosting score for search hits in name by 2 sorts + * documents by relevance. + * + * @param name + * @param description + * @return + */ + @Query + List findTop10ByNameOrDescription(@Boost(2) String name, String description); } diff --git a/solr/example/src/main/java/example/springdata/solr/ProductRepositoryCustom.java b/solr/example/src/main/java/example/springdata/solr/ProductRepositoryCustom.java index 0a686993..e968db9b 100644 --- a/solr/example/src/main/java/example/springdata/solr/ProductRepositoryCustom.java +++ b/solr/example/src/main/java/example/springdata/solr/ProductRepositoryCustom.java @@ -25,5 +25,11 @@ import org.springframework.data.solr.core.query.result.Cursor; */ public interface ProductRepositoryCustom { + /** + * Use a {@link Cursor} to scroll through documents in index.
+ * NOTE: Requires at least Solr 4.7. + * + * @return + */ Cursor findAllUsingCursor(); } diff --git a/solr/example/src/test/java/example/springdata/solr/AdvancedSolrRepositoryTests.java b/solr/example/src/test/java/example/springdata/solr/AdvancedSolrRepositoryTests.java new file mode 100644 index 00000000..6717f119 --- /dev/null +++ b/solr/example/src/test/java/example/springdata/solr/AdvancedSolrRepositoryTests.java @@ -0,0 +1,189 @@ +/* + * Copyright 2014-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.solr; + +import static org.hamcrest.core.IsNull.*; +import static org.junit.Assert.*; +import static org.springframework.data.solr.core.query.Criteria.*; +import static org.springframework.data.solr.core.query.ExistsFunction.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.solr.core.SolrOperations; +import org.springframework.data.solr.core.query.Function; +import org.springframework.data.solr.core.query.Query; +import org.springframework.data.solr.core.query.SimpleQuery; +import org.springframework.data.solr.core.query.result.HighlightPage; +import org.springframework.data.solr.repository.Boost; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import example.springdata.solr.test.util.RequiresSolrServer; + +/** + * @author Christoph Strobl + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class AdvancedSolrRepositoryTests { + + public static @ClassRule RequiresSolrServer requiresRunningServer = RequiresSolrServer.onLocalhost(); + + @Configuration + static class Config extends SolrTestConfiguration { + + @Override + protected void doInitTestData(CrudRepository repository) { + + Product playstation = new ProductBuilder().withId("id-1").named("Playstation") + .withDescription("The Sony playstation was the top selling gaming system in 1994.").withPopularity(5).build(); + + Product playstation2 = new ProductBuilder().withId("id-2").named("Playstation Two") + .withDescription("Playstation two is the successor of playstation in 2000.").build(); + + Product superNES = new ProductBuilder().withId("id-3").named("Super Nintendo").withPopularity(3).build(); + + Product nintendo64 = new ProductBuilder().withId("id-4").named("N64").withDescription("Nintendo 64") + .withPopularity(2).build(); + + repository.save(Arrays.asList(playstation, playstation2, superNES, nintendo64)); + } + } + + @Autowired ProductRepository repo; + @Autowired SolrOperations operations; + + /** + * {@link HighlightPage} holds next to the entities found also information about where a match was found within the + * document. This allows to fine grained display snipplets of data containing the matching term in context. + */ + @Test + public void annotationBasedHighlighting() { + + HighlightPage products = repo.findByDescriptionStartingWith("play", new PageRequest(0, 10)); + + products.getHighlighted().forEach( + entry -> entry.getHighlights().forEach( + highligh -> System.out.println(entry.getEntity().getId() + " | " + highligh.getField() + ":\t" + + highligh.getSnipplets()))); + } + + /** + * Using {@link Boost} allows to influence scoring at query time. In this case we want hits in {@code Product#name} to + * count twice as much as such in {@code Product#description}. + */ + @Test + public void annotationBasedBoosting() { + + repo.findTop10ByNameOrDescription("Nintendo", "Nintendo") // + .forEach(System.out::println); + } + + /** + * Using {@link Function} in queries has no influence on restricting results as all documents will match the function. + * Though it does influence document score. In this sample documents not having popularity assigned will be sorted to + * the end of the list. + */ + @Test + public void influcenceScoreWithFunctions() { + + operations.queryForPage(new SimpleQuery(where(exists("popularity"))).addProjectionOnFields("*", "score"), + Product.class) // + .forEach(System.out::println); + } + + /** + * Using {@link SolrOperations#getById(java.io.Serializable, Class)} allows reading uncommitted documents from the + * update log. + */ + @Test + public void useRealtimeGetToReadUncommitedDocuments() throws InterruptedException { + + Product xbox = new ProductBuilder().withId("id-5").named("XBox").withDescription("Microsift XBox") + .withPopularity(2).build(); + Query query = new SimpleQuery(where("id").is(xbox.getId())); + + // add document but delay commit for 3 seconds + operations.saveBean(xbox, 3000); + + // document will not be returned hence not yet committed to the index + assertThat(operations.queryForObject(query, Product.class), nullValue()); + + // realtime-get fetches uncommitted document + assertThat(operations.getById(xbox.getId(), Product.class), notNullValue()); + + // wait a little so that changes get committed to the index - normal query will now be able to find the document. + Thread.sleep(3010); + assertThat(operations.queryForObject(query, Product.class), notNullValue()); + } + + static class ProductBuilder { + + private Product product; + + public ProductBuilder() { + this.product = new Product(); + } + + public ProductBuilder withId(String id) { + this.product.setId(id); + return this; + } + + public ProductBuilder named(String name) { + this.product.setName(name); + return this; + } + + public ProductBuilder withDescription(String description) { + this.product.setDescription(description); + return this; + } + + public ProductBuilder withPopularity(Integer popularity) { + this.product.setPopularity(popularity); + return this; + } + + public ProductBuilder inCategory(String category) { + + List categories = new ArrayList<>(); + categories.add(category); + + if (this.product.getCategory() == null) { + categories.addAll(this.product.getCategory()); + } + + this.product.setCategory(categories); + return this; + + } + + public Product build() { + return this.product; + } + + } +} diff --git a/solr/example/src/test/java/example/springdata/solr/SolrRepositoryTests.java b/solr/example/src/test/java/example/springdata/solr/BasicSolrRepositoryTests.java similarity index 79% rename from solr/example/src/test/java/example/springdata/solr/SolrRepositoryTests.java rename to solr/example/src/test/java/example/springdata/solr/BasicSolrRepositoryTests.java index 973ef3de..598e62f2 100644 --- a/solr/example/src/test/java/example/springdata/solr/SolrRepositoryTests.java +++ b/solr/example/src/test/java/example/springdata/solr/BasicSolrRepositoryTests.java @@ -15,13 +15,10 @@ */ package example.springdata.solr; -import java.util.Iterator; - import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.solr.core.query.result.Cursor; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -32,7 +29,7 @@ import example.springdata.solr.test.util.RequiresSolrServer; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { SolrTestConfiguration.class }) -public class SolrRepositoryTests { +public class BasicSolrRepositoryTests { public static @ClassRule RequiresSolrServer requiresRunningServer = RequiresSolrServer.onLocalhost(); @@ -43,9 +40,8 @@ public class SolrRepositoryTests { */ @Test public void findAll() { - - Iterator iterator = repo.findAll().iterator(); - printResult(iterator); + repo.findAll()// + .forEach(System.out::println); } /** @@ -53,15 +49,8 @@ public class SolrRepositoryTests { */ @Test public void findAllUsingDeepPagination() { - - Cursor cursor = repo.findAllUsingCursor(); - printResult(cursor); + repo.findAllUsingCursor()// + .forEachRemaining(System.out::println); } - private void printResult(Iterator it) { - - while (it.hasNext()) { - System.out.println(it.next()); - } - } } diff --git a/solr/example/src/test/java/example/springdata/solr/SolrTestConfiguration.java b/solr/example/src/test/java/example/springdata/solr/SolrTestConfiguration.java index 0fc56886..4e370bf5 100644 --- a/solr/example/src/test/java/example/springdata/solr/SolrTestConfiguration.java +++ b/solr/example/src/test/java/example/springdata/solr/SolrTestConfiguration.java @@ -33,7 +33,7 @@ import org.springframework.data.solr.core.SolrTemplate; @EnableAutoConfiguration public class SolrTestConfiguration { - @Autowired CrudRepository repo; + private @Autowired CrudRepository repo; @Bean public SolrTemplate solrTemplate() { @@ -53,6 +53,10 @@ public class SolrTestConfiguration { */ @PostConstruct public void initWithTestData() { + doInitTestData(repo); + } + + protected void doInitTestData(CrudRepository repository) { for (int i = 0; i < 100; i++) { @@ -60,7 +64,7 @@ public class SolrTestConfiguration { p.setId("p-" + i); p.setName("foobar"); - repo.save(p); + repository.save(p); } } }