My code has the class Credenciada that inherits from class Cadastro that has the atributte(nested class) "cep" of CEP type.
The CEP class has the atributte "uf" of String type.
The uf atributte was annoted with #SortableField, so the index is save with "cep.uf" name. But when I need sort by "cep.uf" a NullPointerException is throwed, because the Hibernate-search didn't find this index/atributte.
OBS: I saw that attribute "allFieldDescriptors" of class IndexedTypeDescriptorImpl didn't have the "cep.uf" ocurrency. Because in line 139 of this method returns null on this point "return allFieldDescriptors.get(fieldName)".
Cadastro.java:
#Entity
#Table(name = "CAD")
#Inheritance(strategy = InheritanceType.JOINED)
#Indexed
public class Cadastro extends Entidade<Long> {
#Id
private Long codigo;
#IndexedEmbedded
#NotAudited
#ManyToOne
private CEP cep;
//more attrs and getters/setters here
}
Credenciada.java:
#Entity
#Indexed
#Table(name = "CAD_CRDC")
#PrimaryKeyJoinColumn(name = "CD_CAD_CRDC")
public class Credenciada extends Cadastro {
//many attrs and getters/setters here
}
CEP.java
#Entity
#Table(name = "CAD_CEP", schema = "dne")
public class CEP extends Entidade<Long> {
#Id
#Field(name = "cep", store = Store.YES)
private Long nrCep;
#SortableField
#Field(store = Store.YES)
private String uf;
}
Search code:
FullTextEntityManager fullTextEntityManager = hibernateUtil.getFullTextEntityManager();
QueryBuilder qb = HibernateSearchUtil.createQueryBuilder(fullTextEntityManager, Credenciada.class);
BooleanJunction<?> criterios = qb.bool();
//many conditions here...
org.apache.lucene.search.Query rootQuery = criterios.isEmpty() ? new MatchAllDocsQuery() : criterios.createQuery();
FullTextQuery query = fullTextEntityManager.createFullTextQuery(rootQuery, Credenciada.class);
query.setSort(qb.sort().byField("cep.uf").createSort());
List resultList = query.getResultList();
Exception:
java.lang.NullPointerException
at org.hibernate.search.query.dsl.sort.impl.SortFieldStates.getCurrentSortFieldTypeFromMetamodel(SortFieldStates.java:156)
at org.hibernate.search.query.dsl.sort.impl.SortFieldStates.determineCurrentSortFieldTypeAutomaticaly(SortFieldStates.java:149)
at org.hibernate.search.query.dsl.sort.impl.ConnectedSortContext.byField(ConnectedSortContext.java:42)
at br.gov.sindec.modulos.credenciada.repositorio.impl.CredenciadaRepositorioImpl.criarQueryListarCredenciadasIndexadas(CredenciadaRepositorioImpl.java:124)
at br.gov.sindec.modulos.credenciada.repositorio.impl.CredenciadaRepositorioImpl.listarCredenciadasIndexadas(CredenciadaRepositorioImpl.java:52)
Hibernate Search 5.6.1.Final and 5.7.0.Final have just been released; both versions include a fix for your issue (HSEARCH-2587).
Related
I have 2 java classes, one of them contains the other as a list by #ElementCollection.
When I try to set that list, following error occurs:
Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.1.3.v20110304-r9073): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
Error Code: 942
Call: SELECT t0.VERSIONS FROM MainProcess_VERSIONS t0 WHERE (t0.MainProcess_SUBJECT_ID = ?)
bind => [#id]
Query: DirectReadQuery(name="versions" sql="SELECT t0.VERSIONS
FROM MainProcess_VERSIONS t0 WHERE (t0.MainProcess_SUBJECT_ID = ?)")
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:333)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:683)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:526)
.
.
.
at ...Editor.MAINPROCESS._persistence_propertyChange(MAINPROCESS.java)
at ...Editor.MAINPROCESS._persistence_set_versions(MAINPROCESS.java)
at ...MAINPROCESS.setVersions(MAINPROCESS.java:79)
at ...DataModel.fillSubprocessSubject(DataModel.java:643)
at ...DataModel.load(DataModel.java:321)
If I remove the #ElementCollection then I get the following error
Error Code: 942
Call:
SELECT t1.ACT_VERSION, t1.SUBJECT_ID, t1.ACT_VERSION_REMARK,
t1.ACT_VALID_TO, t1.ACT_VERSION_ACCEPTED, t1.NORMALRETURNVALUES, t1.ACT_VALID_FROM
FROM MAINPROCESS_MAINPROCESSVERSION t0, MAINPROCESSVERSION t1
WHERE ((t0.MainProcess_SUBJECT_ID = ?) AND ((t1.SUBJECT_ID = t0.SUBJECT_ID)
AND (t1.ACT_VERSION = t0.ACT_VERSION)))
bind => [#id]
Here are my classes,
MainProcess:
#Entity
public class MainProcess implements Serializable {
#Id
#Column(name = "SUBJECT_ID", nullable = false)
private Long subjectId;
...other columns
#ElementCollection
private List<MainProcessVersion> versions = new ArrayList<MainProcessVersion>();
public MainProcess() {
}
public void setVersions(List<MainProcessVersion> versions) {
this.versions = versions;
}
public List<MainProcessVersion> getVersions() {
return versions;
}
other getters and setters...
}
MainProcessVersion:
#Entity
#IdClass(MainProcessVersionPK.class)
public class MainProcessVersion implements Serializable {
#Id
#Column(name = "SUBJECT_ID", nullable = false)
#XmlTransient
private Long subjectId;
#Id
#Column(name = "ACT_VERSION")
private Long actVersion;
...other columns
private List<String> normalReturnValues;
public MainProcessVersion() {
}
getters and setters...
}
And the function in which the error occurs:
private MainProcess fillSubprocessSubject(Long Id) {
MainProcess p = someFacade.getProcess(Id);
List<MainProcessVersion> versions = someFacade.getProcessVersions(Id);
p.setVersions(versions); //error is here
return p;
}
Thanks in advance for any help.
I managed to solve my problem. Most important was to understand the difference between #ElementCollection and #OneToMany annotation. I cannot better explain like this.
So the problem was that my MainProcessVersion.java was annotated with #Entity, thus I could not add the #ElementCollection annotation to it in the MainProcess.java.
#ElementCollection //One cannot use this annotation because MainProcessVersion is an entity.
private List<MainProcessVersion> versions = new ArrayList<MainProcessVersion>();
But I need the Entity, so I created resultRow class wich is the same as my Entity class except the annotation. After query selection, I cast my result to my new resultRow class and I can set that list in the MainProcess.java.
My resultRow class:
public class MainProcessVersionResultRow implements Serializable {
private Long subjectId;
...other code
}
My old MainProcess class
#Entity
public class MainProcess implements Serializable {
#Id
#Column(name = "SUBJECT_ID", nullable = false)
private Long subjectId;
...other columns
//without #ElementCollection and the new resultRow object type
private List<MainProcessVersionResultRow> versions = new ArrayList<MainProcessVersionResultRow>();
public MainProcess() {
}
...getters. setters
}
And then, I "cast" my entity to resultRow class:
private MainProcess fillSubprocessSubject(Long subjectId) {
MainProcess p =
someFacade.getSubprocessSubject(subjectId);
List<MainProcessVersion> versions = someFacade.getProcessVersions(subjectId);
List<MainProcessVersionResultRow> versionResultRows = new ArrayList<MainProcessVersionResultRow>();
for (MainProcessVersion vs : versions) {
MainProcessVersionResultRow versionSingleResultRow = new MainProcessVersionResultRow(vs);
versionSingleResultRow.setNormalReturnValues(processReturnValues(p.getSubjectId(), vs.getActVersion()));
versionResultRows.add(versionSingleResultRow);
}
p.setVersions(versionResultRows); //does not throw an error any more
return p;
}
So conclusion:
Do not use #ElementCollection on a class that has an #Entity annotation.
I have the following entities:
#AllArgsConstructor
#EqualsAndHashCode(of = {"name"})
#Data
#NoArgsConstructor
#Entity
#Table(schema = "eat")
public class Pizza {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="pizza_id_seq")
private Integer id;
#NotNull
private String name;
#NotNull
#Positive
private Double cost;
#ManyToMany
#JoinTable(schema = "eat",
name = "pizza_ingredient",
inverseJoinColumns = { #JoinColumn(name = "ingredient_id") })
private Set<Ingredient> ingredients;
}
#AllArgsConstructor
#EqualsAndHashCode(of = {"name"})
#Data
#NoArgsConstructor
#Entity
#Table(schema = "eat")
public class Ingredient {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="ingredient_id_seq")
private Integer id;
#NotNull
#Size(min=1, max=64)
private String name;
}
I'm using JPASQLQuery object provided by QueryDSL (4.2.2) to create some native queries in PostgreSQL:
public JPASQLQuery<T> getJPASQLQuery() {
return new JPASQLQuery<>(
entityManager,
PostgreSQLTemplates.builder().printSchema().build()
);
}
The problem comes trying to use join functions, for example:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath("pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
JPASQLQuery subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
// The error is in next innerJoin
.innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient)
.where(ingredient.name.in(ingredientNames));
If I keep the current innerJoin((SubQueryExpression<?>) pizza.ingredients, ingredient) I receive:
class com.querydsl.core.types.dsl.SetPath cannot be cast to class com.querydsl.core.types.SubQueryExpression
I cannot remove current (SubQueryExpression<?>) because innerJoin doesn't accept SetPathas parameter.
On the other hand, the following:
.from(pizza)
.innerJoin(ingredient)
Doesn't work due to pizza_ingredient is not included in the generated query.
How can I use innerJoin in JPASQLQuery with a many to many relationship like above?
Basically, there are two main approaches trying to solve it:
Include required native functions
As suggest one QueryDSL developer here, replacing JPASQLQuery by JPA alternatives.
Create required Path for many to many table
First is important to add name property into every #Table annotation because internally is the one used by QueryDSL NativeSQLSerializer class to generate from and join clauses.
So, for example:
#Table(schema = "eat")
public class Pizza ...
Should be replaced by:
#Table(name = "pizza", schema = "eat")
public class Pizza ...
Next, create for custom Path for the many to many table:
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
So the complete code would be:
QIngredient ingredient = QIngredient.ingredient;
QPizza pizza = QPizza.pizza;
RelationalPathBase<Object> pizzaIngredient = new RelationalPathBase<>(Object.class, "pi", "eat", "pizza_ingredient");
NumberPath<Integer> pizzaIngredient_PizzaId = Expressions.numberPath(Integer.class, pizzaIngredient, "pizza_id");
NumberPath<Integer> pizzaIngredient_IngredientId = Expressions.numberPath(Integer.class, pizzaIngredient, "ingredient_id");
StringPath ingredientPath = Expressions.stringPath("ingredient");
StringPath pizzaPath = Expressions.stringPath( "pizza");
NumberPath<Double> costPath = Expressions.numberPath(Double.class, "cost");
Expression rowNumber = SQLExpressions.rowNumber().over().partitionBy(ingredientPath).orderBy(costPath.desc()).as("rnk");
NumberPath<Long> rnk = Expressions.numberPath(Long.class, "rnk");
SubQueryExpression subQuery = getJPASQLQuery()
.select(ingredient.name.as(ingredientPath), pizza.name.as(pizzaPath), pizza.cost.as(costPath), rowNumber)
.from(pizza)
.innerJoin(pizzaIngredient).on(pizzaIngredient_PizzaId.eq(pizza.id))
.innerJoin(ingredient).on(ingredient.id.eq(pizzaIngredient_IngredientId))
.where(ingredient.name.in(ingredientNames));
return getJPASQLQuery()
.select(ingredientPath, pizzaPath, costPath)
.from(
subQuery,
Expressions.stringPath("temp")
)
.where(rnk.eq(1l))
.fetch();
I have two following entities:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "affiliate_programs")
#SequenceGenerator(name = AbstractEntity.GENERATOR, sequenceName = "affiliate_programs_seq", allocationSize = 1)
public class AffiliateProgram extends AbstractAuditableDeletableEntity {
private static final int DESCRIPTION_LENGTH = 512;
#Column(nullable = false)
private String title;
#OneToMany(mappedBy = "affiliateProgram", fetch = FetchType.LAZY)
private Set<AffiliateProgramStatistics> statistics;
public enum SortType implements ISortType {
ID(QAffiliateProgram.affiliateProgram.id),
TITLE(QAffiliateProgram.affiliateProgram.title),
#Getter
private ComparableExpressionBase[] expressions;
SortType(final ComparableExpressionBase... expressions) {
this.expressions = expressions;
}
}
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "affiliate_programs_statistics")
#SequenceGenerator(name = AbstractEntity.GENERATOR, sequenceName = "affiliate_programs_statistics_seq", allocationSize = 1)
public class AffiliateProgramStatistics extends AbstractAuditableEntity {
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
private AffiliateProgram affiliateProgram;
#Enumerated(EnumType.STRING)
private EventType eventType;
private LocalDate date;
public enum EventType {
MERCHANTS,
PRIORITY_MERCHANTS,
COUPONS,
CLICKS
}
}
I am trying to fetch only the columns from AffiliateProgramStatistics that match the SQL between condition. My SQL query looks like this:
select *
from affiliate_programs ap
left join affiliate_programs_statistics aps on ap.id = aps.affiliate_program_id
where ap.deleted = false and aps.date between '2020-07-20' and '2020-08-20';
And the result of this query is exactly what I need - I get only columns that are NOT marked as deleted AND columns with date BETWEEN required dates.
I tried to write that query in QueryDSL and that's what I came up with:
#Repository
#RequiredArgsConstructor
public class AffiliateProgramsCustomRepositoryImpl implements AffiliateProgramsCustomRepository {
private final EntityManager entityManager;
#Override
public Page<AffiliateProgram> search(final AffiliateProgramSearchForm form) {
final QAffiliateProgram affiliateProgram = QAffiliateProgram.affiliateProgram;
final QAffiliateProgramStatistics affiliateProgramStatistics = QAffiliateProgramStatistics.affiliateProgramStatistics;
final JPAQuery<AffiliateProgram> query = new JPAQuery<AffiliateProgram>(entityManager)
.distinct()
.from(affiliateProgram)
.leftJoin(affiliateProgram.statistics, affiliateProgramStatistics)
.where(AffiliateProgramsRepositoryHelper.getPredicates(form))
.orderBy(AffiliateProgramsRepositoryHelper.getOrders(form.getSorting()))
.limit(form.getLimit())
.offset(form.getOffset());
return AffiliateProgramsRepositoryHelper.pageBy(query, form);
}
}
public class AffiliateProgramsRepositoryHelper extends RepositoryHelper {
public static Predicate[] getPredicates(final AffiliateProgramSearchForm form) {
final QAffiliateProgramStatistics affiliateProgramStatistics = QAffiliateProgramStatistics.affiliateProgramStatistics;
final List<Predicate> predicates = new ArrayList<>();
final String formattedQuery = form.getFormattedQuery();
if (!isNullOrEmpty(formattedQuery)) {
predicates.add(affiliateProgramStatistics.affiliateProgram.title.likeIgnoreCase(formattedQuery));
}
predicates.add(affiliateProgramStatistics.date.between(form.getFrom(), form.getTo()));
predicates.add(affiliateProgramStatistics.affiliateProgram.deleted.isFalse());
return predicates.toArray(new Predicate[0]);
}
}
But the result of this is not satisfying. If at least one of the columns in AffiliateProgramStatistics match the between() condition, it fetches every single column from the table that matches tje leftJoin() condition.
How can I fetch only the columns that I need?
P.S. Hibernate generates the following query:
Hibernate:
select distinct
affiliatep0_.id as id1_0_,
affiliatep0_.created_date_time as created_2_0_,
affiliatep0_.last_modified_date_time as last_mod3_0_,
affiliatep0_.deleted as deleted4_0_,
affiliatep0_.clicks_count as clicks_c5_0_,
affiliatep0_.coupons_count as coupons_6_0_,
affiliatep0_.description as descript7_0_,
affiliatep0_.merchants_count as merchant8_0_,
affiliatep0_.priority_merchants_count as priority9_0_,
affiliatep0_.priority_order as priorit10_0_,
affiliatep0_.title as title11_0_
from affiliate_programs affiliatep0_
inner join affiliate_programs_statistics statistics1_ on affiliatep0_.id=statistics1_.affiliate_program_id
cross join affiliate_programs affiliatep2_
where statistics1_.affiliate_program_id=affiliatep2_.id
and (statistics1_.date between ? and ?)
and affiliatep2_.deleted=?
order by affiliatep0_.title desc nulls last limit ?
which works perfectly and fetches only the data I need if i run it in console
JPA supports the ON clause in JPQL since 2.1, and QueryDSL is able to generate that ON clause in queries. Hibernate had a precedessor for the ON clause in the form of the now deprecated WITH clause. The ON clause can be used in more occasions.
Just use .on(Predicate) immediately after the join on which it should be applied:
final JPAQuery<AffiliateProgram> query = new JPAQuery<AffiliateProgram>(entityManager)
.distinct()
.from(affiliateProgram)
.leftJoin(affiliateProgram.statistics, affiliateProgramStatistics)
.on(AffiliateProgramsRepositoryHelper.getPredicates(form))
.orderBy(AffiliateProgramsRepositoryHelper.getOrders(form.getSorting()))
.limit(form.getLimit())
.offset(form.getOffset());
(column and variable name changed after posting question)i am writing join query using entityManager.createNativeQuery(somequery) in jpa custom method when i run code i get following error :
com.ibm.db2.jcc.am.SqlException: [jcc][10150][10300][4.12.56] Invalid >parameter: Unknown column name exc_seq_nbr. ERRORCODE=-4460, SQLSTATE=null
i am using IBM DB2 server and spring boot
exceptionTenderPK (object in entity class) is not being mapped correctly thats why getting invalid column can someone please tell me how to map exceptionTenderPK object class
Note: i cant use #OneToMany in this case because tables are unrelated
#Entity
#Table(name = "Table_name")
#Data
public class MainPojoclass {
#EmbeddedId
#JsonProperty(value = "mainPojoclassPK")
private MainPojoclassPK mainPojoclassPK;
#Column(name = "amt")
#JsonProperty(value = "amt")
private BigDecimal amt;
#Column(name = "tndid")
#JsonProperty(value = "tndid")
private String tndid;
#Column(name = "cde")
#JsonProperty(value = "cde")
private String cde;
#Column(name = "ind")
#JsonProperty(value = "ind")
private String ind;
#Column(name = "user")
#JsonProperty(value = "user")
private String user;
#Column(name = "updatedtime")
#JsonProperty(value = "updatedtime")
private Date updatedtime;
#Column(name = "src")
#JsonProperty(value = "src")
private String src;
#Column(name = "stat")
#JsonProperty(value = "stat")
private String stat;
}
#Transactional
public interface JoinQueryRepository extends JpaRepository<MainPojoclass, Long>, JoinQueryRepositoryCustom{
}
public interface JoinQueryRepositoryCustom {
List<MainPojoclass> getGRDetails(MainPojoclass et,Date reportDate);
}
public class JoinQueryRepositoryImpl implements JoinQueryRepositoryCustom {
#PersistenceContext
EntityManager entityManager;
#SuppressWarnings("all")
#Override
public List<MainPojoclass> getGRDetails(MainPojoclass et,Date rdate) {
String queryStr = "select et.Salss_DTE from table et"
+ " join dte etr on et.Salss_DTE = etr.Salss_DTE where et.nbr =? ";
List<MainPojoclass> datalist = null;
Query query = entityManager.
createNativeQuery(queryStr,"mapping")
.setParameter(1, 222);
datalist = query.getResultList();
return datalist;
}
}
The error says that there is no column exc_seq_nbr and you used that in your EntityResult mapping.
In your query you only return et.SLS_DTE you have to return all columns that are in the result set mapping.
Hi all since i am not getting any solutions i am going with below solution it works for me and removing #SqlResultSetMapping below code is working without sql result set mapping
Query q = em.createNativeQuery(queryStr);
List<Object[]> resultList = q.getResultList();
for (Object[] result : resultList) {
entityObj.setReason(result[0].toString);
//rest attribute will convert from result[1].toString to corresponding
// data type and set to entity object
}
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.
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.