2009-10-12 22 views
20

Cada vez que intento establecer una lista como parámetro para usar en una expresión IN, aparece una excepción de argumento Ilegal. Varias publicaciones en Internet parecen indicar que esto es posible, pero ciertamente no funciona para mí. Estoy usando Glassfish V2.1 con Toplink.Establecer un parámetro como una lista para una expresión IN

¿Alguien más ha podido hacer que esto funcione? Si es así, ¿cómo?

aquí es un código de ejemplo:

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

y la parte correspondiente de la traza de la pila:

 
java.lang.IllegalArgumentException: You have attempted to set a value of type class java.util.Arrays$ArrayList for parameter accountIds with expected type of class java.lang.Long from query string 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 

Respuesta

18

encontré la respuesta, proporcionando una lista como un parámetro no está soportado en JPA 1.0; sin embargo, es compatible con JPA 2.0.

El proveedor de persistencia predeterminado para Glassfish v2.1 es Toplink que implementa JPA 1.0, para obtener JPA 2.0 necesita EclipseLink que es el predeterminado para la vista previa de Glassfish v3 o se puede conectar a v2.1.

- Loren

+10

y su JPQL está mal, retire los soportes – James

+2

incorrecto. Depende de la implementación con JPA 1.0 implementado por Hibernate, pero necesita paréntesis. – Guaido79

+0

Esto solía ser un error en Hibernate: https://hibernate.atlassian.net/browse/HHH-5126 – Kawu

0

Ah, y si no se puede utilizar EclipseLink por alguna razón, entonces aquí es un método que puede utilizar para agregar los bits necesarios para su consulta. Simplemente inserta la cadena resultante en tu consulta donde pondrías "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 > size; i++) { 
      queryString = queryString.concat(" "+field+" = '"+collection.get(i)+"'"); 
      if(i > size-1) { 
       queryString = queryString.concat(" OR"); 
      } 
     } 
     queryString = queryString.concat(")"); 
     return queryString; 
    } 
9

Espero que esto ayude a alguien. Me he enfrentado a la cuestión e hizo lo siguiente para resolver (usando EclipseLink 2.2.0)

  1. tuve frasco JavaEE, así como JPA 2 frasco (javax.persistence * 2 *) en la ruta de clase. Se eliminó el JavaEE de la ruta de clase.

  2. que estaba usando algo así como " idItm IN (:itemIds) " que fue lanzando la excepción:

tipo java.util.ArrayList clase para ItemIDs de parámetros con el resultado esperado tipo de java.lang.String clase de cadena de consulta

Solución: Acabo de cambiar la condición en " idItm IN :itemIds ", es decir, eliminé los soportes().

+0

muchas gracias @dillip que salvó mi día. Solo tuve que eliminar los paréntesis alrededor del parámetro – Sofiane

-3

También puede probar esta sintaxis.

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

y poner en consulta, "Select * from Class where field in " + Class.generateCollection(list);

+2

No, no, no. Esto permitirá las posibilidades de inyección de SQL. – siebz0r

37

Usted JPQL está mal, quitar los soportes

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

Desafortunadamente no, tuve el mismo error ... tenemos Java EE 5, por lo tanto, EJB 3.0. De la especificación: "JSR 220: Enterprise JavaBeans, Versión 3.0 - API Java Persistence" Sección I veo que "In" Expression requiere corchetes ... pero luego estaba obteniendo IllegalArgumentException cuando establecí el parámetro como List ... así que estoy utilizando el espantoso hack de más abajo para expandir los ID en una cadena de OR (hasta que pueda obtener las asignaciones de JPA adecuadas en su lugar con una vista de base de datos para evitar eso). –

+0

Te amo <3 <3 – Rob

+0

No funcionó para mí con .setParameter(), tuve que usar setParameterList() – lukas84

0

Prueba este código en lugar de la suministrada por @Szymon Tarnowski para añadir el o lista. advertencia si tiene cientos de ID, puede romper el límite establecido con respecto a la longitud máxima de una consulta.

/** 
* @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; 
} 

Para probar esta:

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

Qué dio salida: Y (myfield = '122' OR myfield = '132' OR myfield = '112')

+7

Repita después de mí: "Nunca usaré StringBuilders u otras técnicas similares para crear consultas SQL, ya que esto permite la inyección SQL. solo usará declaraciones preparadas y tal ". – siebz0r

+1

Gracias por la advertencia sobre posibles vulnerabilidades con SQL @ siebz0r. Sin embargo, no creo que esto sea válido aquí. No se pueden usar declaraciones preparadas ya que no se conoce el número de argumentos y la versión de JPA no permite listas de argumentos. Además, los argumentos aquí no son de entrada pública y se emiten como objetos enteros. –

+0

Entiendo que los riesgos son limitados, pero aun así, pueden suceder cosas extrañas. 'java.sql.PreparedStatement' se puede usar para estos casos excepcionales. Esta interfaz admite colecciones como un parámetro. – siebz0r

1

Simplemente, el Lista de parámetros será y configurarlo como

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

Esto funciona para JPA 1.0

+0

no funciona –

1

Uso NamedQuery lugar:

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

Añadir la consulta con nombre a su entidad

@NamedQuery(name = "Account.findByIdList", query = "SELECT a.accountManager.loginName FROM Account a WHERE a.id IN :ids") 
Cuestiones relacionadas