Angel Bonilla
Angel Bonilla

Reputation: 11

Hibernate search returning empty results when using spring based information system locally deployed using tomcat

I have the following issue: I am developing a spring based information system, using Hibernate, MySql5.5, Java 7,and Apache Tomcat 7.0. The requirement I have to fullfil specifies the use of a finder in order find tasks. These tasks have to be found using a keyword, a max date, min date, a max price, a min price, a category name and a warranty title. The keyword must appear in any of the following task attributes: ticker (alphanumerical identifier), description and address. The range of prices apply to the task attribute price, and the range of dates to both the start date and the end date of the task. Of course both the category name and the warranty title apply to the ones associated with a certain task

I have developed an implementation of this finder using hibernate search, and when I execute jUnit test for my finder it actually returns the correct list of tasks. The problem comes when I try to test this finder with the information system locally deployed using Tomcat. Even when I use the same parameters that I used in the jUnit test, the result is an empty list. Also, whenever I execute the submit of the editing form that represents the search of task my eclipse console throws the following warning message: [http-bio-8080-exec-9] WARN org.hibernate.jpa.internal.EntityManagerFactoryRegistry - HHH00043 6: Entity manager factory name (Acme-HandyWorker) is already registered. If entity manager will be clustered or passiva ted, specify a unique value for property 'hibernate.ejb.entitymanager_factory_name'. I do not know how this previous warning affects to my issue.

Here you have the UML domain model of the entities previously specified


Task

@Indexed
@Entity
@Access(AccessType.PROPERTY)
public class Task extends DomainEntity {

private String ticker;
private Date publicationMoment;
private String description;
private String address;
private double maxPrice;
private Date startDate;
private Date endDate;
private Warranty warranty;
private Category category;
private Collection<Complaint> complaints;
private Customer customer;
private Collection<Application> applications;

@Field
@NotBlank
@Column(unique = true)
@Pattern(regexp = "^[0-9]{6}-[A-Z0-9]{6}$")
public String getTicker() {
return this.ticker;
}

public void setTicker(final String ticker) {
this.ticker = ticker;
}

@Past
@NotNull
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "dd/MM/yyyy HH:mm")
public Date getPublicationMoment() {
return this.publicationMoment;
}

public void setPublicationMoment(final Date publicationMoment) {
this.publicationMoment = publicationMoment;
}

@Field
@NotBlank
public String getDescription() {
return this.description;
}

public void setDescription(final String description) {
this.description = description;
}

@Field
@NotBlank
public String getAddress() {
return this.address;
}

public void setAddress(final String address) {
this.address = address;
}

@Min(0)
@Digits(integer = 99, fraction = 2)
@Field
@NumericField
public double getMaxPrice() {
return this.maxPrice;
}

public void setMaxPrice(final double maxPrice) {
this.maxPrice = maxPrice;
}

@Past
@NotNull
@Temporal(TemporalType.DATE)
@DateTimeFormat(pattern = "dd/MM/yyyy")
@Field
public Date getStartDate() {
return this.startDate;
}

public void setStartDate(final Date startDate) {
this.startDate = startDate;
}

@NotNull
@Temporal(TemporalType.DATE)
@DateTimeFormat(pattern = "dd/MM/yyyy")
@Field
public Date getEndDate() {
return this.endDate;
}

public void setEndDate(final Date endDate) {
this.endDate = endDate;
}

@Valid
@ManyToOne(optional = false)
@IndexedEmbedded
public Warranty getWarranty() {
return this.warranty;
}

public void setWarranty(final Warranty warranty) {
this.warranty = warranty;
}

@Valid
@ManyToOne(optional = false)
@IndexedEmbedded
public Category getCategory() {
return this.category;
}

public void setCategory(final Category category) {
this.category = category;
}

@NotNull
@OneToMany
public Collection<Complaint> getComplaints() {
return this.complaints;
}

public void setComplaints(final Collection<Complaint> complaints) {
this.complaints = complaints;
}

@Valid
@ManyToOne(optional = false)
public Customer getCustomer() {
return this.customer;
}

public void setCustomer(final Customer customer) {
this.customer = customer;
}

@NotNull
@OneToMany(mappedBy = "task")
public Collection<Application> getApplications() {
return this.applications;
}

public void setApplications(final Collection<Application> applications) {
this.applications = applications;
}

}

Warranty

@Entity
@Access(AccessType.PROPERTY)
public class Warranty extends DomainEntity {

private String              title;
private Collection<String>  terms;
private Collection<String>  laws;
private String              mode;


@NotBlank
@Field
public String getTitle() {
    return this.title;
}

public void setTitle(final String title) {
    this.title = title;
}

@NotEmpty
@ElementCollection
public Collection<String> getTerms() {
    return this.terms;
}

public void setTerms(final Collection<String> terms) {
    this.terms = terms;
}

@NotEmpty
@ElementCollection
public Collection<String> getLaws() {
    return this.laws;
}

public void setLaws(final Collection<String> laws) {
    this.laws = laws;
}

@NotBlank
@Pattern(regexp = "^(DRAFT|FINAL)$")
public String getMode() {
    return this.mode;
}

public void setMode(final String mode) {
    this.mode = mode;
}

}

Category

@Entity
@Access(AccessType.PROPERTY)
public class Category extends DomainEntity {

private String      name;
private String      nameEs;
private Category    father;


@NotBlank
@Field
public String getName() {
    return this.name;
}

public void setName(final String name) {
    this.name = name;
}

@NotBlank
@Field
public String getNameEs() {
    return this.nameEs;
}

public void setNameEs(final String nameEs) {
    this.nameEs = nameEs;
}

@Valid
@ManyToOne(optional = true)
public Category getFather() {
    return this.father;
}

public void setFather(final Category father) {
    this.father = father;
}

}

Finder

@Entity
@Access(AccessType.PROPERTY)
public class Finder extends DomainEntity {

private String keyWord;
private Double minPrice;
private Double maxPrice;
private Date minDate;
private Date maxDate;
private String categoryName;
private String warrantyTitle;
private Collection<Task> tasks;
private Date moment;

@NotNull
public String getKeyWord() {
return this.keyWord;
}

public void setKeyWord(final String keyWord) {
this.keyWord = keyWord;
}

@Min(0)
@Digits(integer = 99, fraction = 2)
public Double getMinPrice() {
return this.minPrice;
}

public void setMinPrice(final Double minPrice) {
this.minPrice = minPrice;
}

@Min(0)
@Digits(integer = 99, fraction = 2)
public Double getMaxPrice() {
return this.maxPrice;
}

public void setMaxPrice(final Double maxPrice) {
this.maxPrice = maxPrice;
}

@Temporal(TemporalType.DATE)
@DateTimeFormat(pattern = "dd/MM/yyyy")
public Date getMinDate() {
return this.minDate;
}

public void setMinDate(final Date minDate) {
this.minDate = minDate;
}

@Temporal(TemporalType.DATE)
@DateTimeFormat(pattern = "dd/MM/yyyy")
public Date getMaxDate() {
return this.maxDate;
}

public void setMaxDate(final Date maxDate) {
this.maxDate = maxDate;
}

@NotNull
public String getCategoryName() {
return this.categoryName;
}

public void setCategoryName(final String categoryName) {
this.categoryName = categoryName;
}

@NotNull
public String getWarrantyTitle() {
return this.warrantyTitle;
}

public void setWarrantyTitle(final String warrantyTitle) {
this.warrantyTitle = warrantyTitle;
}

@NotNull
@ManyToMany
public Collection<Task> getTasks() {
return this.tasks;
}

public void setTasks(final Collection<Task> tasks) {
this.tasks = tasks;
}

@NotNull
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "dd/MM/yyyy")
public Date getMoment() {
return this.moment;
}

public void setMoment(final Date moment) {
this.moment = moment;
}

}

pom.xml

<!-- Hibernate -->

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.0.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>4.3.1.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-c3p0</artifactId>
        <version>4.2.3.Final</version>
    </dependency>

    <!-- Hibernate Full-text search -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-search</artifactId>
        <version>4.5.3.Final</version>
    </dependency>

persistence.xml

<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

<persistence-unit name="Acme-HandyWorker">
    <properties>
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/Acme-HandyWorker" />
        <property name="javax.persistence.jdbc.user" value="acme-manager" />
        <property name="javax.persistence.jdbc.password" value="ACME-M@n@ger-6874" />

        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
        <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />

        <!-- Hibernate Full-text search -->
        <property name="hibernate.search.default.directory_provider" value="org.hibernate.search.store.impl.FSDirectoryProvider"/>
        <property name="hibernate.search.default.indexBase" value="var/lucene/indexes"/>
    </properties>

</persistence-unit>

Filter tasks implementation

public List<Task> filterTasks() {
    final Finder f = this.finderService.findOne();

    final String keyword = f.getKeyWord().toLowerCase();
    final double minPrice = f.getMinPrice();
    final double maxPrice = f.getMaxPrice();
    final Date minDate = f.getMinDate();

    final Date maxDate = f.getMaxDate();
    final String category = f.getCategoryName().toLowerCase();
    final String warranty = f.getWarrantyTitle().toLowerCase();

    final ConfigurationParameters conf = this.configurationParametersService.find();
    final int max = conf.getMaxResults();

    final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Acme-HandyWorker");
    final EntityManager em = entityManagerFactory.createEntityManager();
    final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

    em.getTransaction().begin();

    final QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Task.class).get();

    final org.apache.lucene.search.Query checkKeyword = qb.bool().should(qb.keyword().wildcard().onField("ticker").matching("*" + keyword + "*").createQuery())
        .should(qb.keyword().wildcard().onField("description").matching("*" + keyword + "*").createQuery()).should(qb.keyword().wildcard().onField("address").matching("*" + keyword + "*").createQuery()).createQuery();

    final org.apache.lucene.search.Query checkCategory = qb.bool().should(qb.keyword().wildcard().onField("category.name").matching("*" + category + "*").createQuery())
        .should(qb.keyword().wildcard().onField("category.nameEs").matching("*" + category + "*").createQuery()).createQuery();

    final org.apache.lucene.search.Query query = qb.bool().must(checkKeyword).must(qb.range().onField("maxPrice").from(minPrice).to(maxPrice).createQuery()).must(qb.range().onField("startDate").above(minDate).createQuery())
        .must(qb.range().onField("endDate").below(maxDate).createQuery()).must(checkCategory).must(qb.keyword().wildcard().onField("warranty.title").matching("*" + warranty + "*").createQuery()).createQuery();

    final FullTextEntityManager fullTextSession = Search.getFullTextEntityManager(fullTextEntityManager);
    final org.hibernate.search.jpa.FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(query);

    fullTextQuery.setMaxResults(max);

    // execute search
    final List result = fullTextQuery.getResultList();

    em.getTransaction().commit();
    em.close();

    return result;
}

jUnit test

    @Test
public void testSave() {
    super.authenticate("handyworker40");

    final Finder f = this.finderService.findOne();
    f.setMaxPrice(120.);
    final String maxDateInString = "20/11/2018";
    final Date maxDate = this.defaultDate(maxDateInString);
    final String minDateInString = "06/04/2018";
    final Date minDate = this.defaultDate(minDateInString);
    f.setMaxDate(maxDate);
    f.setMinDate(minDate);

    final Finder saved = this.finderService.save(f);

    final Collection<Finder> fs = this.finderService.findAll();
    System.out.println(saved.getTasks());

    Assert.isTrue(fs.contains(saved));

}
private Date defaultDate(final String dateInString) {
    final SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
    Date res = new Date();
    try {
        res = formatter.parse(dateInString);
    } catch (final ParseException e) {
        throw new RuntimeException(e);
    }

    return res;
}

The sysout printed in console:

[domain.Task{id=3804, version=0}, domain.Task{id=3807, version=0}, domain.Task{id=3813, version=0}, domain.Task{id=3818,
 version=0}, domain.Task{id=3819, version=0}, domain.Task{id=3826, version=0}, domain.Task{id=3827, version=0}]

The finder controller

@RequestMapping(value = "/edit", method = RequestMethod.GET)
public ModelAndView edit() {
    ModelAndView result;
    Finder finder;
    final boolean clear;

    finder = this.finderService.findOne();

    clear = this.finderService.clearCache(finder);

    if (clear) {
        final Collection<Task> empty = new ArrayList<>();
        finder.setTasks(empty);
    }

    result = this.createEditModelAndView(finder);

    return result;
}

@RequestMapping(value = "/edit", method = RequestMethod.POST, params = "find")
public ModelAndView save(@Valid final Finder finder, final BindingResult binding) {
    ModelAndView result;

    if (binding.hasErrors())
        result = this.createEditModelAndView(finder);
    else
        try {
            this.finderService.save(finder);
            result = new ModelAndView("redirect:display.do");
        } catch (final Throwable oops) {
            result = this.createEditModelAndView(finder, "finder.commit.error");
        }

    return result;
}

protected ModelAndView createEditModelAndView(final Finder finder) {
    ModelAndView result;

    result = this.createEditModelAndView(finder, null);

    return result;
}

protected ModelAndView createEditModelAndView(final Finder finder, final String messageCode) {
    final ModelAndView result;
    Collection<Warranty> warranties;
    Collection<Category> categories;
    double maxPrice;
    String lang;

    lang = LocaleContextHolder.getLocale().getLanguage();

    warranties = this.warrantyService.findAll();
    categories = this.categoryService.findAll();
    maxPrice = this.taskService.findMaxPrice();

    result = new ModelAndView("finder/edit");
    result.addObject("finder", finder);
    result.addObject("warranties", warranties);
    result.addObject("categories", categories);
    result.addObject("maxPrice", maxPrice);
    result.addObject("lang", lang);

    result.addObject("message", messageCode);

    return result;
}

Finder service save method

    public Finder save(final Finder f) {
    Assert.notNull(f);

    final HandyWorker principal = this.handyWorkerService.findByPrincipal();
    final Date moment = new Date(System.currentTimeMillis());
    f.setMoment(moment);

    if (f.getMaxDate().equals(null)) {
        final String maxDateInString = "31/12/9999";
        final Date maxDate = this.defaultDate(maxDateInString);
        f.setMaxDate(maxDate);
    } else if (f.getMinDate().equals(null)) {
        final String minDateInString = "31/12/999";
        final Date minDate = this.defaultDate(minDateInString);
        f.setMinDate(minDate);
    } else if (f.getMaxPrice().equals(null)) {
        final double maxPrice = this.taskService.findMaxPrice();
        f.setMaxPrice(maxPrice);
    } else if (f.getMinPrice().equals(null))
        f.setMinPrice(0.);

    final Finder saved = this.finderRepository.save(f);

    if (f.getId() == 0) {
        principal.setFinder(saved);
        this.handyWorkerService.save(principal);
    } else
        Assert.isTrue(saved.equals(f));

    final Collection<Task> filteredTasks = this.taskService.filterTasks();
    saved.setTasks(filteredTasks);

    return this.finderRepository.save(saved);
}

edit.jsp

<form:form action="finder/handyWorker/edit.do" modelAttribute="finder">

<form:hidden path="id" />
<form:hidden path="version" />
<form:hidden path="tasks" />
<form:hidden path="moment" />

<form:label path="keyWord">
    <spring:message code="finder.keyWord" />:
</form:label>
<form:input path="keyWord"/>
<br/>

<form:label path="minPrice">
    <spring:message code="finder.minPrice" />:
</form:label>
<form:input path="minPrice" type="number" min="0" max="${maxPrice}"/>
<form:errors cssClass="error" path="minPrice" />
<br />

<form:label path="maxPrice">
    <spring:message code="finder.maxPrice" />:
</form:label>
<form:input path="maxPrice" type="number" min="0" max="${maxPrice}"/>
<form:errors cssClass="error" path="maxPrice" />
<br />

<form:label path="minDate">
    <spring:message code="finder.minDate" />:
</form:label>
<form:input path="minDate"/>
<form:errors cssClass="error" path="minDate" />
<br />

<form:label path="maxDate">
    <spring:message code="finder.maxDate" />:
</form:label>
<form:input path="maxDate"/>
<form:errors cssClass="error" path="maxDate" />
<br />

<form:label path="categoryName">
    <spring:message code="finder.categoryName" />:
</form:label>
<form:input path="categoryName"/>
<br/>

<form:label path="warrantyTitle">
    <spring:message code="finder.warrantyTitle" />:
</form:label>
<form:input path="warrantyTitle"/>
<br/>

<input type="submit" name="find" value="<spring:message code="finder.save" />" />

Here you can see the editing view with the same parameters as in the jUNit test

And this is the result in the display result view: result

Upvotes: 0

Views: 676

Answers (1)

yrodiere
yrodiere

Reputation: 9977

First, a warning: executing the following piece of code at runtime is really dubious.

final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Acme-HandyWorker");

You are creating an entity manager factory each time the filterTasks() method is executed. But the entity manager factory is intended to be a singleton, re-used across all of your applications, in every thread. You generally shouldn't have more than one instance of the entity manager factory for a given persistence unit.

I encourage you to use a framework that manages entity manager factories and entity managers for you. Spring Boot is one, WildFly another, but I'm sure pretty much any framework you'll find will do this for you.

If you really want to handle it yourself, then at least create the entity manager factory when your application is bootstrapped, store it somewhere for retrieval by your runtime methods (like filterTasks()), and make sure to close it when your application shuts down.

About your problem

The most likely explanation is that you did not index your data. In the test, you persist data to your database, so Hibernate Search will pick up the write events and index on the fly. In production, the data is (from what I understand) already persisted, so Hibernate Search hasn't had the opportunity to index it.

To index what is already in the database, have a look at mass indexing; essentially you will need to run something like this the first time you boot your application:

EntityManager em = entityManagerFactory.createEntityManager();
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
fullTextEntityManager.createIndexer().startAndWait();

There are many options to the mass indexer, please refer to the documentation to use what's best for you.

On a side note

Just in case you are building a new application, I would like to mention that the versions of Hibernate ORM and Hibernate Search you are using are ancient, so you are likely to run into bugs that have been fixed in more recent versions. In a new application you should probably use more recent versions. Though I understand you may not have a choice, especially if it's a legacy application.

If you are stuck with Java 7, I would advise to upgrade to Hibernate ORM 5.1 and Hibernate Search 5.6. Ideally you should consider upgrading to Java 8, ORM 5.4 and Search 5.11. The migration will likely require changes in your application, but there are migration guides available on hibernate.org, here for Hibernate Search and [here for Hibernate ORM])(http://hibernate.org/orm/documentation/5.4/) (for ORM, you have to select a version using the select box on the top-right).

Upvotes: 1

Related Questions