Hibernate filter on embedded Entities - java

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.

Related

Join a many to many relation with QueryDSL and JPASQLQuery

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();

How to fetch only columns that satisfy the condition in one-to-many relationship in QueryDSL?

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());

Filter entities by a condition within a collection

The simplified question would be:
Having in a “A” entity, a collection of “B” entities (from a OneToMany relationship) and using CriteriaBuilder, how to get the elements from "A" that have in this collection at least one element "B" that meets a condition? (for example B has a property equal to 10, or as another example B points to another entity whose property is equal to 10).
To put in context:
In my program I have the following entities
#Entity
#SequenceGenerator(name = "sequence", sequenceName = "S_EXPEDIENT")
public class Expedient {
private Long idExpedient
private Integer numberExpedient
private Integer yearExpedient
#ManyToOne
#JoinColumn(name = "id_chamber")
private Chamber chamber
private Integer status
#OneToMany(cascade = CascadeType.ALL, mappedBy = "expedient")
private List<HistoricalAssignment> assignments = new ArrayList<HistoricalAssignment>();
//more fields
}
#SequenceGenerator(name = "sequence", sequenceName = "S_HISTORICAL_ASSIGNMENT ")
#Entity
public class HistoricalAssignment {
private Long idHistoricalAssignment
#ManyToOne
#JoinColumn(name = "id_expedient")
private Expedient expedient;
#ManyToOne
#JoinColumn(name = "id_office")
private Office office;
//more fields
}
#SequenceGenerator(name = "sequence", sequenceName = "S_OFFICE")
#Entity
public class Office extends {
private Long idOffice
#ManyToOne
#JoinColumn(name = "id_chamber")
private Chamber chamber
//more fields
}
#SequenceGenerator(name = "sequence", sequenceName = "S_CHAMBER")
#Entity
public class Chamber{
private Long id_chamber
private String description;;
//more fields
}
I am also using Spring Data Specifications to perform searches with filters.
The problem I have is when trying to get all the "Expedients" that were assigned at least once to a "Chamber", since I would have to look for those who have in the collection "assignments" at least one "Office" that belongs to a "Chamber."
I edit by adding more code. Keep in mind that the original code has several levels of inheritance (here simplified) and also the original model is in Spanish:
public class ExpedientSpecification implements Specification<Expedient> {
private final Integer STATUS_DELETED = -1;
protected ExpedientFilter criteria;
protected List<Predicate> predicates = new ArrayList<Predicate>();
public ExpedientSpecification(ExpedientFilter criteria) {
this.criteria=criteria;
}
public Predicate toPredicate(Root<Expedient> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//variable filters
if(criteria.getId() != null){
predicates.add(cb.equal(root.get("id"), criteria.getId()));
}
if(criteria.getNumberExpedient() != null){
predicates.add(criteriaBuilder.equal(root.get("numberExpedient"), criteria.getNumberExpedient()));
}
if(criteria.getYearExpedient() != null){
predicates.add(criteriaBuilder.equal(root.get("yearExpedient"), criteria.getYearExpedient()));
}
//Fixed Filters
predicates.add(criteriaBuilder.notEqual(root.get("status"), STATUS_DELETED));
// This is where I need to add the filter. I need to get only those in the "assignments" List have at least one
// "Office" that belongs to a "Chamber"
Predicate returnPredicates = cb.and(predicates.toArray(new Predicate[this.predicates.size()]));
predicates.clear();
return returnPredicates;
}
}
How are you doing the criteria? Can you post the code?
Anyway you can join oneToMany normally with a criteria alias.
Example: criteria.createAlias("assignments","assignments"); and then add a restriction on the atributte from assignments that you wish to filter.

#SortableField not working in nested entity

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).

Query ElementCollection of Enum by using JPA Criteria API

I'm working of a web application for a car dealer. I have a Car class with a field which contain a set of security enums.
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull(message = "{year}")
#Min(value = 1950)
#Max(value = 2020)
#Column(nullable = false)
private int year;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
#NotNull()
#Min(value = 0)
#Max(value = 1000000)
#Column(nullable = false)
private int kilometres;
#Column(nullable = false)
private int price;
#NotNull()
#Enumerated(EnumType.STRING)
private Gearbox gearbox;
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#CollectionTable(name="SECURITY")
#Column(name="TYPE")
private Set<Security> securityList = new HashSet<Security>();
#NotNull()
#Column(nullable = false)
private String description;
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Picture> pictureList = new ArrayList<Picture>();
// Getters and setters + help methods..
The Security enum is like:
public enum Security {
ABS("abs"),
AIRBAG("airbag"),
ANTISPIN("antispin"),
CENTRAL_LOCKING("centralLocking"),
REMOTE_ALARM("remoteAlarm"),
FOUR_WHEEL("fourWheel"),
PARKING_ASSISTANCE("parkingAssistance"),
SERVICE_MANUAL("serviceManual"),
STABILITY_CONTROL("stabilityControl"),
XENON_LIGHT("xenonLight");
private String label;
private Security(String label) {
}
public String getLabel() {
return label;
}
}
In the web application, I will create a search page, where the users is able to define required Securitiy parts and a manufacturer pattern (make field in Car class) . For instance, a user might search for Cars which have a make pattern according to "Volkswagen" and Security with at least ABS and REMOTE_ALARM.
My problem is that I am not sure how to create the query using the criteria API. I guess it should start like:
public List<Car> searchCars(String makePattern, Set<Security> requiredSecuirtySet) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> _car = cq.from(Car.class);
// Give me some help here please =)
return em.createQuery(cq).getResultList();
}
Can you please help me? I also have a meta model over the Car class.
Best regards and thanks in advance!
You can use collections as parameters so maybe this will work:
TypedQuery<Car> q = em.createQuery("select c from Car c where c.make = :make and c.securityList in :secutiryList", Car.class);
q.setParameter("make", makePattern);
q.setParameter("securityList", requiredSecuirtySet);
return q.getResultList();
I haven't tested this so I'm not sure it will work. It is based on this question. I also haven't worked with the criteria API so I didn't know how to 'translate' it.
Here's a shot at the query with the criteria API:
public List<Car> searchCars(String makePattern,
Set<Security> requiredSecuirtySet)
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> car = query.from(Car.class);
query.select(car).where(
builder.equal(car.get("make"), makePattern),
car.get("securityList").in(requiredSecuirtySet));
return em.createQuery(query).getResultList();
}
Thanks siebz0r!
I was modifying your code a little bit since your code returns all Cars that has 1 or more security (and not all), i.e. returns all cars which has a securityList that contain at least a subset of the securityList.
Here is my code:
public List<Car> searchCars(String makePattern, Set<Security> requiredSecuirtySet) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> car = cq.from(Car.class);
Predicate criteria = cb.conjunction();
for (Security security : carQueryData.getSecurityCriteria()) {
criteria = cb.and(criteria, car.get(Car_.securityList).in(security) );
}
// Add more predicates, for instance:
// for (Equipment equipment : carQueryData.getEquipmentsCriteria()) {
// criteria = cb.and(criteria, car.get(Car_.equipmentList).in(equipment) );
// }
Predicate makePredicate = cb.equal(car.get(Car_.make), makePattern);
cq.select(car).where(makePredicate, criteria);
return em.createQuery(cq).getResultList();
}
Best regards

Categories