简体   繁体   中英

JPA criteria predicate query combining Integer and String arguments

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

Solution for your predicate

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

JPA canonical metamodels

This is just a suggestion, but you also might consider building the canonical metamodels for your entities (eg 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.


Updates

I have missed a few design problems with your entities. There are two possible solutions.

Creating a new Entity field

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.

Using a Join

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM