Reputation: 479
I'm using JPA (EclipseLink flavor), framework JSF 2.0, querying a MySQL contacts table containing 4 fields:
contactId(int Pk), firstName (String), surname(String), countryId(int Foreign key)
The below code works fine if only using string arguments so that the user can enter as many or as few search strings as required (ie: if the search boxes are left blank, this is equivalent to select * and the entire dataset is returned).
The problem is in combining an integer argument (countryId) which is the foreign key that adds countries to the search criteria. After some research, I'm still having trouble understanding the right way to do this.
Does the integer need to be converted to a string representation within the searchContacts method? I gathered that JPA Criteria API provides typcasting but not type conversion methods? If so, What is the best way to include an integer in the below method and pass this integer into the predicateArray?
Thanks in advance!
public ListDataModel<Contacts> searchContacts(String firstname, String surName, int countryId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Contacts> query = cb.createQuery(Contacts.class);
Root<Contacts> cont = query.from(Contacts.class);
query.select(cont);
List<Predicate> predicateList = new ArrayList<Predicate>();
Predicate firstnamePredicate, surnamePredicate, countryPredicate;
if ((firstname != null) && (!(firstname.isEmpty()))) {
firstnamePredicate = cb.like(cb.upper(cont.<String>get("firstname")), "%" + firstname.toUpperCase() + "%");
predicateList.add(firstnamePredicate);
}
if ((surName != null) && (!(surName.isEmpty()))) {
surnamePredicate = cb.like(cb.upper(cont.<String>get("surname")), "%" + surName.toUpperCase() + "%");
predicateList.add(surnamePredicate);
}
// here is where I am stuck and trying the solution suggested by meskobalazs, except I changed null to 0 since countryId is an integer
if (countryId != 0) {
countryPredicate = cb.equal(cont.<Integer>get("countryid"), countryId);
predicateList.add(countryPredicate);
}
Predicate[] predicateArray = new Predicate[predicateList.size()];
predicateList.toArray(predicateArray);
query.where(predicateArray);
ListDataModel<Contacts> contactList = new ListDataModel<Contacts>(em.createQuery(query).getResultList());
return contactList;
}
}
The above caused the following EJB Exception:
Caused by: Exception [EclipseLink-6078] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.QueryException
Exception Description: The class of the argument for the object comparison is incorrect.
Expression: [
Base com.manaar.domains.Contacts]
Mapping: [org.eclipse.persistence.mappings.ManyToOneMapping[countryid]]
Argument: [1]
Query: ReadAllQuery(referenceClass=Contacts )
at org.eclipse.persistence.exceptions.QueryException.incorrectClassForObjectComparison(QueryException.java:595)
at org.eclipse.persistence.mappings.OneToOneMapping.buildObjectJoinExpression(OneToOneMapping.java:287)
at org.eclipse.persistence.internal.expressions.RelationExpression.normalize(RelationExpression.java:803)
The countryid is an integer with a manyToOne mapping in the contacts entity to the country entity. The contacts entity is:
@Entity
@Table(name = "contacts")
@XmlRootElement
public class Contacts implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "CONTACTSID")
private Integer contactsid;
@JoinColumn(name = "countryid", referencedColumnName = "COUNTRYID")
@ManyToOne
private Country countryid;
The country entity is:
@Entity
@Table(name = "country")
@XmlRootElement
public class Country implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "COUNTRYID")
private Integer countryid;
@Size(max = 255)
@Column(name = "COUNTRYNAME")
private String countryname;
.....
Upvotes: 0
Views: 6332
Reputation: 16041
The Predicate
class is not typed (generic), so you don't need to collect different predicates any differently. This should work just fine:
if (countryId != null) {
countryPredicate = cb.equal(cont.<Integer>get("countryid"), countryId);
predicateList.add(countryPredicate);
}
This is just a suggestion, but you also might consider building the canonical metamodels for your entities (e.g. with Apache Dali), so you can get
your fields in a typesafe manner.
Instead of
cont.<MyType>get("typeName")
you could write:
cont.get(_MyType.typeName)
This is especially useful, if you are refactoring your entities, because you might forget updating the Strings, but you can't forget updating your metamodels, because the code does not even compile otherwise.
I have missed a few design problems with your entities. There are two possible solutions.
The first one is creating an actual Integer
field in Contacts
and map it to the same field as countryId
.
So you should rename the Country
field to country
, and create a new field.
@Column(name = "countryid", insertable = false, updatable = false)
private Integer countryId;
@JoinColumn(name = "countryid", referencedColumnName = "countryid")
@ManyToOne
private Country country;
After you do this, you are able to use my original solution.
The second solution is using a Join
in your query.
if (countryId != null) {
Join<Contacts, Country> country = cont.join("countryid");
countryPredicate = cb.equal(country.<Integer>get("countryId"), countryId);
predicateList.add(countryPredicate);
}
Upvotes: 3