2009-10-12 9 views
20

Ogni volta che provo a impostare un elenco come parametro da utilizzare in un'espressione IN, ottengo un'eccezione argomento non valido. Vari post su internet sembrano indicare che ciò è possibile, ma sicuramente non funziona per me. Sto usando Glassfish V2.1 con Toplink.Impostazione di un parametro come elenco per un'espressione IN

Qualcun altro è stato in grado di farlo funzionare, in caso affermativo come?

ecco qualche esempio di codice:

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

e la parte rilevante l'analisi dello stack:

 
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 

risposta

18

ho trovato la risposta, fornendo un elenco come parametro non è supportato in JPA 1.0; tuttavia, è supportato in JPA 2.0.

Il provider di persistenza predefinito per Glassfish v2.1 è Toplink che implementa JPA 1.0, per ottenere JPA 2.0 è necessario EclipseLink, che è l'impostazione predefinita per l'anteprima di Glassfish v3 o può essere collegata alla v2.1.

- Loren

+10

e la tua JPQL è sbagliato, rimuovere le staffe – James

+2

sbagliato. Dipende dall'implementazione con JPA 1.0 implementata da Hibernate, ma necessita di parentesi. – Guaido79

+0

Questo era un bug in Hibernate: https://hibernate.atlassian.net/browse/HHH-5126 – Kawu

0

Oh, e se non è possibile utilizzare EclipseLink per qualche ragione, allora qui è un metodo che è possibile utilizzare per aggiungere i bit necessari alla tua richiesta. Basta inserire la stringa risultante nella query in cui inserire "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

Spero che questo aiuti qualcuno. Ho affrontato la questione e ha fatto la seguente per risolvere (utilizzando EclipseLink 2.2.0)

  1. ho avuto vaso JavaEE così come JPA 2 vaso (javax.persistence * 2 *) nel percorso di classe. Rimosso JavaEE dal percorso della classe.

  2. Stavo usando qualcosa come " idItm IN (:itemIds) " che è stato gettando eccezione:

tipo java.util.ArrayList classe per ItemIDs parametri con previsto tipo di classe java.lang.String dalla stringa di query

Soluzione: ho appena cambiato il in condizione di " idItm IN :itemIds ", cioè ho rimosso le parentesi().

+0

grazie mille @dillip hai salvato la mia giornata. Ho appena dovuto eliminare le parentesi attorno al parametro – Sofiane

-3

Si può anche provare questa sintassi.

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

e messo in interrogazione, "Select * from Class where field in " + Class.generateCollection(list);

+2

No, no, no. Ciò consentirà le possibilità di iniezione SQL. – siebz0r

37

È JPQL è sbagliato, rimuovere le staffe

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

Purtroppo no - Ho avuto lo stesso errore .. abbiamo Java EE 5, quindi EJB 3.0. Dalla Spec: "JSR 220: Enterprise JavaBeans, Versione 3.0 - Java Persistence API" Sezione Vedo che "In" Espressione richiede parentesi ... ma poi ho ricevuto IllegalArgumentException quando ho impostato il parametro come Elenco .. quindi sono usando l'orribile attacco qui sotto di espandere gli ID in una stringa di OR (fino a quando non riesco a ottenere i corretti mapping JPA con una vista DB per evitarlo). –

+0

Ti amo <3 <3 – Rob

+0

Non ha funzionato con me .setParameter(), ho dovuto usare setParameterList() – lukas84

0

Prova questo codice al posto di quello fornito da @Szymon Tarnowski per aggiungere la lista OR. avviso se si dispone di centinaia di ID, si potrebbe rompere qualsiasi limite è in atto per quanto riguarda la lunghezza massima di una query.

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

Per verificare questa:

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

che ha dato in uscita: AND (MyField = '122' O myfield = '132' O myfield = '112')

+7

Ripeti dopo di me: "Non utilizzerò mai StringBuilder o altre tecniche simili per creare query SQL poiché ciò consente l'iniezione SQL. utilizzerà solo istruzioni preparate e simili. " – siebz0r

+1

Grazie per l'avvertimento sulle potenziali vulnerabilità con SQL @ siebz0r. Comunque non credo che questo sia valido qui. Le istruzioni preparate non possono essere utilizzate poiché il numero di argomenti non è noto e la versione di JPA non consente gli argomenti di elenco. Inoltre, gli argomenti qui non provengono da un input pubblico e vengono lanciati come oggetti Integer. –

+0

Capisco che i rischi sono limitati, ma comunque possono accadere cose strane. 'java.sql.PreparedStatement' può essere usato per casi rari. Questa interfaccia supporta le collezioni come parametro. – siebz0r

1

Semplicemente, la parametro sarà List e impostarlo come

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

Questo funziona per JPA 1.0

+0

non funziona –

1

Uso NamedQuery invece:

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

Aggiungi la query di nome al vostro soggetto

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