简体   繁体   English

结合Integer和String参数的JPA条件谓词查询

[英]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: 我正在使用JPA(EclipseLink风格)框架JSF 2.0,查询包含4个字段的MySQL联系人表:

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. 问题在于结合使用整数参数(countryId),该参数是将国家/地区添加到搜索条件中的外键。 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? 是否需要在searchContacts方法中将整数转换为字符串表示形式? I gathered that JPA Criteria API provides typcasting but not type conversion methods? 我收集到JPA Criteria API提供类型转换,但不提供类型转换方法? If so, What is the best way to include an integer in the below method and pass this integer into the predicateArray? 如果是这样,在下面的方法中包括一个整数并将该整数传递给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: 以上导致以下EJB异常:

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. countryid是一个整数,在contact实体与country实体之间具有manyToOne映射。 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. Predicate类没有类型化(泛型),因此您不需要以不同的方式收集不同的谓词。 This should work just fine: 这应该可以正常工作:

if (countryId != null) {
    countryPredicate = cb.equal(cont.<Integer>get("countryid"), countryId);
    predicateList.add(countryPredicate);
}

JPA canonical metamodels JPA规范元模型

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. 这只是一个建议,但您也可以考虑为您的实体构建规范的元模型(例如,使用Apache Dali),以便以类型安全的方式get字段。

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 . 第一个是在Contacts创建一个实际的Integer字段,并将其映射到与countryId相同的字段。

So you should rename the Country field to country , and create a new field. 因此,您应该将Country字段重命名为country ,然后创建一个新字段。

@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. 第二种解决方案是在查询中使用Join

if (countryId != null) {
    Join<Contacts, Country> country = cont.join("countryid");
    countryPredicate = cb.equal(country.<Integer>get("countryId"), countryId);
    predicateList.add(countryPredicate);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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