TM00
TM00

Reputation: 1390

Hibernate filter on embedded Entities

I have a Dropwizard application which uses hibernate search. In a nutshell I have animals and their data in my database and I am trying to add a filter to get animals of a specific list of breeds. Unfortunately it seems that my filter doesn't work and I cant figure out why. I suspect it may be how I defined the relation between my entities and how I try to reference the breed attribute in the FilterFactory, but it seems right to me. I have implemented my filter based on the Hibernate documentation. The relevant classes are as follows:

My Animal class:

@Entity
@Indexed
@FullTextFilterDef(name = "animalFilter", impl = AnimalFilterFactory.class)
@Table(name = "animal")
@JsonIgnoreProperties(ignoreUnknown = true)
public class Animal implements Serializable{

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @IndexedEmbedded
    @OneToOne(fetch = FetchType.LAZY,
            cascade =  CascadeType.ALL,
            mappedBy = "animal")
    private AnimalInfo info;
// rest of the class goes here
}

My AnimalInfo class:

@Entity
@Table(name = "animal_info")
@JsonIgnoreProperties(ignoreUnknown = true)
public class AnimalInfo implements Serializable{

    @Id
    @Column(name = "animal_id")
    private Long animalId;

    @IndexedEmbedded
    @ManyToOne( cascade = CascadeType.ALL )
    @JoinColumn(name="breed_id")
    private AnimalBreed breed;
// rest of the class goes here
}

My AnimalBreed class:

@Entity
@Table(name = "animal_breeds")
@JsonIgnoreProperties(ignoreUnknown = true)
public class AnimalBreed implements Serializable{

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * name of the breed which I want to filter my animals on.
     */
    @Field
    @Column(name = "name")
    private String name;

And my filter class: Maybe the "info.breed.name" statement is incorrect?

public class AnimalFilterFactory {
    // list of breed names
    private List<String> breeds;

    public void setBreeds(List<String> breeds) {
        this.breeds = breeds;
    }    
    @Factory
    public Query getFilter() {

        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
         // apply filter for all the breeds
        for (String breed : breeds) {
            booleanQuery.add(new TermQuery( new Term( "info.breed.name", breed ) ), Occur.SHOULD);
        }
        return booleanQuery.build();
    }

}

And in my DAO class where I want to apply the filter:

public List<Animal> getForBreeds(List<String> breedNames){
        EntityManager em = this.currentSession();

        FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

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

        qb.sort().byNative(SortField.FIELD_SCORE).andByNative(new SortField("id", Type.STRING, true));


        org.apache.lucene.search.Query luceneQuery = qb.all().createQuery(); // get all the items 

        // apply filter
        FullTextQuery ftq = fullTextEntityManager.createFullTextQuery(luceneQuery, Animal.class);
        ftq.enableFullTextFilter("animalFilter").setParameter("breeds", breedNames);


        return ftq.getResultList();
}

I have tested that, without applying the filter, all the Animal entities in my DB are returned. So it seems that the filter is definitely the issue.

Any help is appreciated.

Upvotes: 0

Views: 587

Answers (1)

TM00
TM00

Reputation: 1390

Turns out, my approach was correct, I just misunderstood how Lucene indexes its terms. for example the breed "Boston Terrier" is indexed as "boston" and "terrier". To filter the entire phrase, a PhraseQuery must be used instead of a TermQuery. I updated my filter as follows:

@Factory
    public Query getFilter() {

        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();

        String fieldId = "info.breed.name";

        //booleanQuery.setMinimumNumberShouldMatch(1);
        for (String breed : breeds) {

            if(breed.contains(" ")) { // if multiple terms in breed
                PhraseQuery.Builder builder = new PhraseQuery.Builder();

                String[] terms = breed.split(" ");

                for (int i = 0; i < terms.length; i++) {
                    builder.add(new Term( fieldId, terms[i].toLowerCase() ), i);
                }

                PhraseQuery pq = builder.build();

                BooleanClause clause = new BooleanClause(pq, Occur.SHOULD);
                booleanQuery.add(clause);
            }else {
                // single term
                BooleanClause clause = new BooleanClause(new TermQuery( new Term(fieldId, breed.toLowerCase() ) ), Occur.SHOULD);
                booleanQuery.add(clause);
            }


        }

        BooleanQuery query = booleanQuery.build();
        return query;
    }

For the record, I figured this out by using Luke to inspect the indexed entity fields and adjust my code accordingly. Just make sure you use the correct version of Luke for your Lucene indexes as there are version incompatibilities. The Luke and Lucene versions run parallel to each other. In my case I used version 5.5.

Upvotes: 1

Related Questions