将参数设置为一个列表以用于IN表达式

32
每当我尝试将列表设置为用于IN表达式的参数时,我都会收到一个非法参数异常。互联网上的各种帖子似乎表明这是可能的,但对我来说肯定不起作用。我正在使用带有Toplink的Glassfish V2.1。
是否有其他人能够使这个工作,如果是,怎么做?
这里有一些示例代码:
List<String> logins = em.createQuery("SELECT a.accountManager.loginName " +
    "FROM Account a " +
    "WHERE a.id IN (:ids)")
    .setParameter("ids",Arrays.asList(new Long(1000100), new Long(1000110)))
    .getResultList();

以及相关的堆栈跟踪部分:

java.lang.IllegalArgumentException: 您已经尝试将类型为 java.util.Arrays$ArrayList 的值设置为参数 accountIds 的预期类型为 java.lang.Long 的值,查询字符串为 SELECT a.accountManager.loginName FROM Account a WHERE a.id IN (:accountIds)。
at oracle.toplink.essentials.internal.ejb.cmp3.base.EJBQueryImpl.setParameterInternal(EJBQueryImpl.java:663)
at oracle.toplink.essentials.internal.ejb.cmp3.EJBQueryImpl.setParameter(EJBQueryImpl.java:202)
at com.corenap.newtDAO.ContactDaoBean.getNotificationAddresses(ContactDaoBean.java:437)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.enterprise.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1011)
at com.sun.enterprise.security.SecurityUtil.invoke(SecurityUtil.java:175)
at com.sun.ejb.containers.BaseContainer.invokeTargetBeanMethod(BaseContainer.java:2920)
at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4011)
at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:203)
... 67 more
9个回答

53

你的JPQL语句有误,请去掉括号。

List<String> logins = em.createQuery("SELECT a.accountManager.loginName " +
    "FROM Account a " +
    "WHERE a.id IN :ids")
    .setParameter("ids",Arrays.asList(new Long(1000100), new Long(1000110)))
    .getResultList();

1
很遗憾 - 我也遇到了同样的错误..我们使用的是Java EE 5,因此是EJB 3.0。从规范中可以看到:"JSR 220: Enterprise JavaBeans,Version 3.0 - Java Persistence API"部分指出,“In”表达式需要括号..但是当我将参数设置为List<Integer>时,我会收到IllegalArgumentException..所以我正在使用下面这个可怕的hack来将ID扩展为一个OR字符串(直到我能够通过DB视图放置正确的JPA映射来避免这种情况)。 - Robert Mark Bram
我使用.setParameter()时无法正常工作,必须使用setParameterList()。 - lukas84
如何将类似于 var ids = "a1","b1",...; 的字符串传递给 Java 函数,并进而传递给 IN 运算符? - Venky
3
@lukas84 很可能是因为您正在使用 hibernate Query,而不是 persistence Query - Draken

20

我找到了答案,JPA 1.0 不支持将列表作为参数传递;然而,JPA 2.0 支持这个特性。

Glassfish v2.1 的默认持久化提供程序是 Toplink,它实现了 JPA 1.0。要使用 JPA 2.0,您需要使用 EclipseLink,它是 Glassfish v3 预览版的默认提供程序,也可以插入到 v2.1 中。

- Loren


3
错误。这取决于使用Hibernate实现的JPA 1.0的具体实现,它可以工作,但需要括号。 - Guaido79
1
这曾经是Hibernate中的一个错误:https://hibernate.atlassian.net/browse/HHH-5126 - Kawu

15
希望这对某些人有所帮助。我遇到了这个问题,并采取了以下措施来解决(使用eclipselink 2.2.0):
  1. 我在类路径中同时包含了JavaEE jar和jpa 2 jar(javax.persistence*2*)。从类路径中移除JavaEE。

  2. 我正在使用类似于" idItm IN ( :itemIds ) "的语句,它会抛出以下异常:

类型为java.util.ArrayList的项itemIds无法与查询字符串期望的类java.lang.String匹配。

解决方案:我只需将in条件更改为" idItm IN :itemIds ",即删除括号。


非常感谢@dillip,你救了我的一天。我只需要删除参数周围的括号。 - Sofiane

2

简单来说,参数将是一个列表,并将其设置为

"...WHERE a.id IN (:ids)")
.setParameter("ids", yourlist)

这适用于JPA 1.0版本。


2

IN :ids instead of IN (:ids) - will work.


0

使用 NamedQuery 替代:

List<String> logins = em.createNamedQuery("Account.findByIdList").setParameter("ids", Arrays.asList(new Long(1000100), new Long(1000110))).getResultList();

将命名查询添加到您的实体中。
@NamedQuery(name = "Account.findByIdList", query = "SELECT a.accountManager.loginName FROM Account a WHERE a.id IN :ids")

0

另外,如果由于某种原因您无法使用 EclipseLink,则可以使用以下方法向查询中添加所需的位。只需将生成的字符串插入到查询中,在您会放置“a.id IN (:ids)” 的地方即可。

    /** 
     * @param field The jpql notation for the field you want to evaluate
     * @param collection The collection of objects you want to test against
     * @return Jpql that can be concatenated into a query to test if a feild is in a collection of objects
     */
    public String in(String field, List collection) {
        String queryString = new String();
        queryString = queryString.concat(" AND (");
        int size = collection.size();
        for(int i = 0; i &gt size; i++) {
            queryString = queryString.concat(" "+field+" = '"+collection.get(i)+"'");
            if(i &gt size-1) {
                queryString = queryString.concat(" OR");
            }
        }
        queryString = queryString.concat(" )");
        return queryString;
    }

-1

尝试使用这段代码替换@Szymon Tarnowski提供的代码以添加OR列表。 警告如果您有数百个ID,您可能会破坏查询最大长度限制。

/**
 * @param field
 *           The jpql notation for the field you want to evaluate
 * @param collection
 *           The collection of objects you want to test against
 * @return Jpql that can be concatenated into a query to test if a feild is
 *         in a collection of objects
 */
public static String in(String field, List<Integer> idList) {
  StringBuilder sb = new StringBuilder();
  sb.append(" AND (");
  for(Integer id : idList) {
    sb.append(" ").append(field).append(" = '").append(id).append("'").append(" OR ");
  }
  String result = sb.toString();
  result = result.substring(0, result.length() - 4); // Remove last OR
  result += ")";
  return result;
}

为了测试这个:

public static void main(String[] args) {
  ArrayList<Integer> list = new ArrayList<Integer>();
  list.add(122);
  list.add(132);
  list.add(112);
  System.out.println(in("myfield", list));
}

代码的输出为: AND ( myfield = '122' OR myfield = '132' OR myfield = '112')


7
跟着我说:“我永远不会使用StringBuilders或其他类似的技术来构建SQL查询,因为这会导致SQL注入。我只会使用预处理语句等安全的方法。” - siebz0r
2
谢谢您对 SQL 潜在漏洞的警告,@siebz0r。但是我不认为这在这里是有效的。由于参数数量未知且 JPA 版本不允许列表参数,因此无法使用预处理语句。此外,这里的参数不来自公共输入,并且它们被转换为整数对象。 - Robert Mark Bram
我知道风险是有限的,但是仍然可能发生奇怪的事情。java.sql.PreparedStatement 可以用于这种罕见情况。该接口支持集合作为参数。 - siebz0r

-3
你也可以尝试这种语法。
static public String generateCollection(List list){
    if(list == null || list.isEmpty())
        return "()";
    String result = "( ";
    for(Iterator it = list.iterator();it.hasNext();){
        Object ob = it.next();
        result += ob.toString();
        if(it.hasNext())
            result += " , ";
    }
    result += " )";
    return result;
}

并将其放入查询中,"Select * from Class where field in " + Class.generateCollection(list);


2
不,不,不。这样会导致 SQL 注入的可能性。 - siebz0r

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接