2011-12-09 19 views
25

Sto tentando di definire una semplice annotazione @Select in MyBatis per ottenere una raccolta di oggetti in base a criteri definiti da una clausola IN. L'SQL assomiglia a qualcosa:Posso passare un elenco come parametro a un mappatore MyBatis?

SELECT * FROM employees WHERE employeeID IN (1, 2, 3); 

L'elenco viene generato dinamicamente, quindi non so quanti parametri avrà. Mi piacerebbe passare solo in un List di valori, qualcosa come:

@Select("SELECT * FROM employees WHERE employeeID IN(#{employeeIds})") 
List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> employeeIds); 

Sto creando un'istanza della Mapper in cui è definito l'annotazione sopra e definendolo come segue:

List<Integer> empIds = Arrays.asList(1, 2, 3); 
List<Employee> result = mapper.selectSpecificEmployees(empIds); 

Ho scoperto che questo non funziona.

org.apache.ibatis.exceptions.PersistenceException:
### Errore database di interrogazione. Causa: java.lang.NullPointerException
### L'errore può comportare
com.mycompany.MySourceMapper.selectSpecificEmployees-Linea
### è verificato l'errore durante l'impostazione dei parametri ### Causa: java.lang.NullPointerException a org.apache.ibatis.exceptions.ExceptionFactory.wrapException (ExceptionFactory.java:8) su org.apache.ibatis.session.defaults.DefaultSqlSession.selectList (DefaultSqlSession.java:77) su org.apache.ibatis.session .defaults.DefaultSqlSession.selectList (DefaultSqlSession.java:69) all'indirizzo org.apache.ibatis.binding.MapperMethod.executeForList (MapperMethod.java:85) all'indirizzo org.apache.ibatis.binding.MapperMethod.execute (M apperMethod.java:65) a org.apache.ibatis.binding.MapperProxy.invoke (MapperProxy.java:35) a $ Proxy23.selectSpecificProductTypes (Fonte sconosciuta) a com.mycompany.MySourceMapperDebug.testSelectSpecificEmployees (MySourceMapperDebug.java: 60) a sun.reflect.NativeMethodAccessorImpl.invoke0 (metodo natale) a sun.reflect.NativeMethodAccessorImpl.invoke (fonte sconosciuta) a sun.reflect.DelegatingMethodAccessorImpl.invoke (fonte sconosciuta) a java.lang.reflect.Method .invoke (sorgente sconosciuta) su junit.framework.TestCase.runTest (TestCase.java:154) su junit.framework.TestCase.runBare (TestCase.java:127) su junit.framework.TestResult $ 1.protect (TestResult .java: 106) a junit.framework.TestResult.runProtected (TestResult.java:124) a junit.framework.TestResult.run (TestResult.java:109) a junit.framework.TestCase.run (TestCase.java:118) in junit.framework.TestSuite.runTest (TestSuite.java:208) all'indirizzo junit.framework.TestSuite.run (TestSuite.java:203) all'indirizzo org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run (JUnit3TestReference.java:130) all'indirizzo org.eclipse.jdt.internal.junit.runner.TestExecution.run (TestExecution.java:38) all'indirizzo org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner. java: 467) all'indirizzo org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner.java:683) all'indirizzo org.eclipse.jdt.internal.junit. runner.RemoteTestRunner.run (RemoteTestRunner.java:390) a org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main (RemoteTestRunner.java:197) provocato da: java.lang.NullPointerException a org.apache .ibatis.type.UnknownTypeHandler.setNonNullParameter (UnknownTypeHandler.java: 21) all'indirizzo org.apache.ibatis.type.BaseTypeHandler.setParameter (BaseTypeHandler.java:23) all'indirizzo org.apache.ibatis.executor.parameter.DefaultParameterHandler.setParameters (DefaultParameterHandler.java:73) presso org. apache.ibatis.executor.statement.PreparedStatementHandler.parameterize (PreparedStatementHandler.java:61) all'indirizzo org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize (RoutingStatementHandler.java:43) su org.apache.ibatis.executor. SimpleExecutor.prepareStatement (SimpleExecutor.java:56) su org.apache.ibatis.executor.SimpleExecutor.doQuery (SimpleExecutor.java:40) su org.apache.ibatis.executor.BaseExecutor.queryFromDatabase (BaseExecutor.java:216) su org.apache.ibatis.executor.BaseExecutor.query (BaseExecutor.java: 95) a org.apache.ibatis.executor.CachingExecutor.query (CachingExecutor.java:72) a sun.reflect.NativeMethodAccessorImpl.invoke0 (metodo natale) a sun.reflect.NativeMethodAccessorImpl.invoke (fonte sconosciuta) a sun.reflect.DelegatingMethodAccessorImpl.invoke (fonte sconosciuta) a java.lang.reflect.Method.invoke (fonte sconosciuta) a org.apache.ibatis.plugin.Invocation.proceed (Invocation.java:31)
.. 36 altro

Penso che il problema sia nell'annotazione stessa. Sembra che sarebbe un requisito abbastanza comune. Devo convertire lo List in un String e inoltrarlo come parametro String anziché List<Integer>? O c'è qualche altra sintassi per passare un List come parametro per un'annotazione MyBatis?

+0

È possibile utilizzare sia la configurazione di xml sia quella basata su annotazione per guardare questo post http://stackoverflow.com/questions/8788250/xml-annotation-based-configuration-for-mybatis/9076435 –

risposta

39

Non ho mai usato annotazioni e MyBatis prima; Ho sempre seguito il percorso del file di configurazione xml (non sottintendendo che ci sia qualcosa di sbagliato nell'usare le annotazioni, solo spiegando che non posso aiutarti).

Detto questo, page 46 from the MyBatis user guide:

foreach

Un'altra necessità comune per SQL dinamico è la necessità di iterare su una collezione , spesso per costruire una condizione di IN. Per esempio:

<select id="selectPostIn" resultType="domain.blog.Post"> 
    SELECT * 
    FROM POST P 
    WHERE ID in 
    <foreach item="item" index="index" collection="list" 
     open="(" separator="," close=")"> 
      #{item} 
    </foreach> 
    </select> 

L'elemento foreach è molto potente, e ti permette di specificare una collezione , dichiareremo l'articolo e indicizzare le variabili che possono essere utilizzate all'interno il corpo dell'elemento. Consente inoltre di specificare l'apertura e le stringhe di chiusura e di aggiungere un separatore per inserirle tra le iterazioni. L'elemento è intelligente in quanto non accidentalmente aggiungere i separatori supplementari .

+0

grazie a questo mi ha aiutato –

+0

Glad per sentirlo! – Dave

+0

Sai se mybatis è in grado di gestire i limiti nelle clausole IN (come oracle è limitato a 1000). Immagino che dovremmo prenderci cura di noi stessi –

4

Con un po 'di overhead è possibile utilizzare JAVA per creare una stringa dinamica dopo aver elaborato l'elenco.

  1. Definire una Selezione fornitore in cui è possibile costruire la vostra query dinamica:

    @SelectProvider(type = com.data.sqlprovider.EmployeeSQLBuilder.class, method =  
    "selectSpecificEmployees") 
    List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> 
        employeeIds); 
    
  2. In com.data.sqlprovider.EmployeeSQLBuilder.di classe, Usare StringBuilder, generare la query

    public String selectSpecificEmployees(Map<String, Object> parameters) { 
        List<Integer> employeeIds = (List<Integer>) parameters.get("employeeIds"); 
        StringBuilder builder = new StringBuilder("SELECT id, name FROM employees where id IN ("); 
        for (int i : employeeIds) { 
         builder.append(i + ","); 
        } 
        builder.deleteCharAt(builder.length() - 1); 
    
        builder.append(")"); 
        System.out.println(builder.toString()); 
        return builder.toString(); 
    } 
    
+2

Poiché i possibili ID dipendenti sono numeri interi, puoi farcela. Tuttavia, se si trattasse di stringhe avresti un problema se la stringa ha caratteri speciali in esso – kasdega

+1

concatenare i valori dei parametri nella stringa SQL è male. E se i parametri sono stringhe, questa è la ricetta per l'iniezione SQL. – blackwizard

0

Se si desidera utilizzare foreach e annotazioni, è possibile utilizzare la seguente sintassi:

@Select("<script>" + 
     "SELECT * FROM employees WHERE employeeID IN " + 
      "<foreach item='item' index='index' collection='employeeIds'" + 
      " open='(' separator=',' close=')'>" + 
      " #{item}" + 
      "</foreach>" + 
     "</script>") 
List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> employeeIds); 

(copiata da quella answer)

4

Sto affrontando esattamente gli stessi problemi di recente. Per mia conoscenza, preferisci utilizzare il mapper Java anziché XML, che è lo stesso qui.

Di seguito è quello che sto facendo per gestirlo utilizzando: SqlBuilder.

La classe costruttore sql:

public class EmployeeSqlBuilder { 

    public String getEmployees(final List employeeIds) { 

     String strSQL = new SQL() {{ 
      SELECT("*"); 
      FROM("employees"); 
      if (employeeIds != null) { 
       WHERE(getSqlConditionCollection("employeeID", employeeIds)); 
      } 
     }}.toString(); 

     return strSQL; 
    } 

    private String getSqlConditionCollection(String field, List conditions) { 
     String strConditions = ""; 
     if (conditions != null && conditions.size() > 0) { 
      int count = conditions.size(); 
      for (int i = 0; i < count; i++) { 
       String condition = conditions.get(i).toString(); 

       strConditions += condition; 
       if (i < count - 1) { 
        strConditions += ","; 
       } 
      } 
      return field + " in (" + strConditions + ")"; 
     } else { 
      return "1=1"; 
     } 
    } 

} 

Il mapper:

@SelectProvider(type = EmployeeSqlBuilder.class, method = "getEmployees") 
List<RecordSubjectEx> getEmployees(@Param("employeeIds") List employeeIds); 

Questo è tutto.

Il EmployeeSqlBuilder genererà dinamicamente l'istruzione sql. Sto usando una funzione getSqlConditionCollection per eseguire la manipolazione logica. Ovviamente è possibile incapsulare lo getSqlConditionCollection come una funzione statica in una classe, che è ciò che sto facendo nel progetto reale, quindi è possibile utilizzarlo facilmente da altri SqlBuilder.

Problemi correlati