BAEL-1336 Introduction to Hibernate Search

This commit is contained in:
Markus Gulden 2017-11-29 23:50:00 +01:00
parent 064ea79490
commit d26adf690b
6 changed files with 562 additions and 0 deletions

View File

@ -57,6 +57,11 @@
<artifactId>jta</artifactId>
<version>${jta.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>${hibernatesearch.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
@ -184,6 +189,7 @@
<!-- persistence -->
<hibernate.version>5.2.10.Final</hibernate.version>
<hibernatesearch.version>5.8.2.Final</hibernatesearch.version>
<mysql-connector-java.version>8.0.7-dmr</mysql-connector-java.version>
<tomcat-dbcp.version>9.0.0.M26</tomcat-dbcp.version>
<jta.version>1.1</jta.version>

View File

@ -0,0 +1,76 @@
package com.baeldung.hibernatesearch;
import com.google.common.base.Preconditions;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
@EnableTransactionManagement
@Configuration
@PropertySource({ "classpath:persistence-h2.properties" })
@EnableJpaRepositories(basePackages = { "com.baeldung.hibernatesearch" })
@ComponentScan({ "com.baeldung.hibernatesearch" })
public class HibernateSearchConfig {
@Autowired
private Environment env;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.baeldung.hibernatesearch.model" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
@Bean
public DataSource dataSource() {
final BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName")));
dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("jdbc.url")));
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", Preconditions.checkNotNull(env.getProperty("hibernate.hbm2ddl.auto")));
properties.setProperty("hibernate.dialect", Preconditions.checkNotNull(env.getProperty("hibernate.dialect")));
return properties;
}
}

View File

@ -0,0 +1,195 @@
package com.baeldung.hibernatesearch;
import com.baeldung.hibernatesearch.model.Product;
import org.apache.lucene.search.Query;
import org.hibernate.search.engine.ProjectionConstants;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class ProductSearchDao {
@PersistenceContext
private EntityManager entityManager;
public List<Product> searchProductNameByKeywordQuery(String text) {
Query keywordQuery = getQueryBuilder()
.keyword()
.onField("productName")
.matching(text)
.createQuery();
List<Product> results = getJpaQuery(keywordQuery).getResultList();
return results;
}
public List<Product> searchProductNameByFuzzyQuery(String text) {
Query fuzzyQuery = getQueryBuilder()
.keyword()
.fuzzy()
.withEditDistanceUpTo(2)
.withPrefixLength(0)
.onField("productName")
.matching(text)
.createQuery();
List<Product> results = getJpaQuery(fuzzyQuery).getResultList();
return results;
}
public List<Product> searchProductNameByWildcardQuery(String text) {
Query wildcardQuery = getQueryBuilder()
.keyword()
.wildcard()
.onField("productName")
.matching(text)
.createQuery();
List<Product> results = getJpaQuery(wildcardQuery).getResultList();
return results;
}
public List<Product> searchProductDescriptionByPhraseQuery(String text) {
Query phraseQuery = getQueryBuilder()
.phrase()
.withSlop(1)
.onField("description")
.sentence(text)
.createQuery();
List<Product> results = getJpaQuery(phraseQuery).getResultList();
return results;
}
public List<Product> searchProductNameAndDescriptionBySimpleQueryStringQuery(String text) {
Query simpleQueryStringQuery = getQueryBuilder()
.simpleQueryString()
.onFields("productName", "description")
.matching(text)
.createQuery();
List<Product> results = getJpaQuery(simpleQueryStringQuery).getResultList();
return results;
}
public List<Product> searchProductNameByRangeQuery(int low, int high) {
Query rangeQuery = getQueryBuilder()
.range()
.onField("memory")
.from(low)
.to(high)
.createQuery();
List<Product> results = getJpaQuery(rangeQuery).getResultList();
return results;
}
public List<Object[]> searchProductNameByMoreLikeThisQuery(Product entity) {
Query moreLikeThisQuery = getQueryBuilder()
.moreLikeThis()
.comparingField("productName")
.toEntity(entity)
.createQuery();
List<Object[]> results = getJpaQuery(moreLikeThisQuery).setProjection(ProjectionConstants.THIS, ProjectionConstants.SCORE)
.getResultList();
return results;
}
public List<Product> searchProductNameAndDescriptionByKeywordQuery(String text) {
Query keywordQuery = getQueryBuilder()
.keyword()
.onFields("productName", "description")
.matching(text)
.createQuery();
List<Product> results = getJpaQuery(keywordQuery).getResultList();
return results;
}
public List<Object[]> searchProductNameAndDescriptionByMoreLikeThisQuery(Product entity) {
Query moreLikeThisQuery = getQueryBuilder()
.moreLikeThis()
.comparingField("productName")
.toEntity(entity)
.createQuery();
List<Object[]> results = getJpaQuery(moreLikeThisQuery).setProjection(ProjectionConstants.THIS, ProjectionConstants.SCORE)
.getResultList();
return results;
}
public List<Product> searchProductNameAndDescriptionByCombinedQuery(String manufactorer, int memoryLow, int memoryTop, String extraFeature, String exclude) {
Query combinedQuery = getQueryBuilder()
.bool()
.must(getQueryBuilder().keyword()
.onField("productName")
.matching(manufactorer)
.createQuery())
.must(getQueryBuilder()
.range()
.onField("memory")
.from(memoryLow)
.to(memoryTop)
.createQuery())
.should(getQueryBuilder()
.phrase()
.onField("description")
.sentence(extraFeature)
.createQuery())
.must(getQueryBuilder()
.keyword()
.onField("productName")
.matching(exclude)
.createQuery())
.not()
.createQuery();
List<Product> results = getJpaQuery(combinedQuery).getResultList();
return results;
}
private FullTextQuery getJpaQuery(org.apache.lucene.search.Query luceneQuery) {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
return fullTextEntityManager.createFullTextQuery(luceneQuery, Product.class);
}
private QueryBuilder getQueryBuilder() {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
return fullTextEntityManager.getSearchFactory()
.buildQueryBuilder()
.forEntity(Product.class)
.get();
}
}

View File

@ -0,0 +1,94 @@
package com.baeldung.hibernatesearch.model;
import org.hibernate.search.annotations.*;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Indexed
@Table(name = "product")
public class Product {
@Id
private int id;
@Field(termVector = TermVector.YES)
private String productName;
@Field(termVector = TermVector.YES)
private String description;
@Field
private int memory;
public Product(int id, String productName, int memory, String description) {
this.id = id;
this.productName = productName;
this.memory = memory;
this.description = description;
}
public Product() {
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Product))
return false;
Product product = (Product) o;
if (id != product.id)
return false;
if (memory != product.memory)
return false;
if (!productName.equals(product.productName))
return false;
return description.equals(product.description);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + productName.hashCode();
result = 31 * result + memory;
result = 31 * result + description.hashCode();
return result;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -9,5 +9,9 @@ hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.show_sql=false
hibernate.hbm2ddl.auto=create-drop
# hibernate.search.X
hibernate.search.default.directory_provider = filesystem
hibernate.search.default.indexBase = /data/index/default
# envers.X
envers.audit_table_suffix=_audit_log

View File

@ -0,0 +1,187 @@
package com.baeldung.hibernatesearch;
import com.baeldung.hibernatesearch.model.Product;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.junit.Assert.*;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Commit;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { HibernateSearchConfig.class }, loader = AnnotationConfigContextLoader.class)
@Transactional
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class HibernateSearchIntegrationTest {
@Autowired
ProductSearchDao dao;
@PersistenceContext
private EntityManager entityManager;
private List<Product> products;
@Before
public void setupTestData() {
products = Arrays.asList(new Product(1, "Apple iPhone X 256 GB", 256, "The current high-end smartphone from Apple, with lots of memory and also Face ID"),
new Product(2, "Apple iPhone X 128 GB", 128, "The current high-end smartphone from Apple, with Face ID"), new Product(3, "Apple iPhone 8 128 GB", 128, "The latest smartphone from Apple within the regular iPhone line, supporting wireless charging"),
new Product(4, "Samsung Galaxy S7 128 GB", 64, "A great Android smartphone"), new Product(5, "Microsoft Lumia 650 32 GB", 32, "A cheaper smartphone, coming with Windows Mobile"),
new Product(6, "Microsoft Lumia 640 32 GB", 32, "A cheaper smartphone, coming with Windows Mobile"), new Product(7, "Microsoft Lumia 630 16 GB", 16, "A cheaper smartphone, coming with Windows Mobile"));
}
@Commit
@Test
public void testA_whenInitialTestDataInserted_thenSuccess() {
for (int i = 0; i < products.size() - 1; i++) {
entityManager.persist(products.get(i));
}
}
@Test
public void testB_whenIndexInitialized_thenCorrectIndexSize() throws InterruptedException {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer()
.startAndWait();
int indexSize = fullTextEntityManager.getSearchFactory()
.getStatistics()
.getNumberOfIndexedEntities(Product.class.getName());
assertEquals(products.size() - 1, indexSize);
}
@Commit
@Test
public void testC_whenAdditionalTestDataInserted_thenSuccess() {
entityManager.persist(products.get(products.size() - 1));
}
@Test
public void testD_whenAdditionalTestDataInserted_thenIndexUpdatedAutomatically() {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
int indexSize = fullTextEntityManager.getSearchFactory()
.getStatistics()
.getNumberOfIndexedEntities(Product.class.getName());
assertEquals(products.size(), indexSize);
}
@Test
public void testE_whenKeywordSearchOnName_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(0), products.get(1), products.get(2));
List<Product> results = dao.searchProductNameByKeywordQuery("iphone");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testF_whenFuzzySearch_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(0), products.get(1), products.get(2));
List<Product> results = dao.searchProductNameByFuzzyQuery("iPhaen");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testG_whenWildcardSearch_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(4), products.get(5), products.get(6));
List<Product> results = dao.searchProductNameByWildcardQuery("6*");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testH_whenPhraseSearch_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(2));
List<Product> results = dao.searchProductDescriptionByPhraseQuery("with wireless charging");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testI_whenSimpleQueryStringSearch_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(0), products.get(1));
List<Product> results = dao.searchProductNameAndDescriptionBySimpleQueryStringQuery("Aple~2 + \"iPhone X\" + (256 | 128)");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testJ_whenRangeSearch_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(0), products.get(1), products.get(2), products.get(3));
List<Product> results = dao.searchProductNameByRangeQuery(64, 256);
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testK_whenMoreLikeThisSearch_thenCorrectMatchesInOrder() {
List<Product> expected = products;
List<Object[]> resultsWithScore = dao.searchProductNameByMoreLikeThisQuery(products.get(0));
List<Product> results = new LinkedList<Product>();
for (Object[] resultWithScore : resultsWithScore) {
results.add((Product) resultWithScore[0]);
}
assertThat(results, contains(expected.toArray()));
}
@Test
public void testL_whenKeywordSearchOnNameAndDescription_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(0), products.get(1), products.get(2));
List<Product> results = dao.searchProductNameAndDescriptionByKeywordQuery("iphone");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
@Test
public void testM_whenMoreLikeThisSearchOnProductNameAndDescription_thenCorrectMatchesInOrder() {
List<Product> expected = products;
List<Object[]> resultsWithScore = dao.searchProductNameAndDescriptionByMoreLikeThisQuery(products.get(0));
List<Product> results = new LinkedList<Product>();
for (Object[] resultWithScore : resultsWithScore) {
results.add((Product) resultWithScore[0]);
}
assertThat(results, contains(expected.toArray()));
}
@Test
public void testN_whenCombinedSearch_thenCorrectMatches() {
List<Product> expected = Arrays.asList(products.get(1), products.get(2));
List<Product> results = dao.searchProductNameAndDescriptionByCombinedQuery("apple", 64, 128, "face id", "samsung");
assertThat(results, containsInAnyOrder(expected.toArray()));
}
}