2011-12-09 27 views
25

Estoy tratando de definir una simple anotación @Select en MyBatis para obtener una colección de objetos en base a los criterios definidos por una cláusula IN. El SQL se ve algo como:¿Puedo pasar una lista como parámetro a un asignador MyBatis?

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

La lista se genera de forma dinámica, por lo que no se sabe cuántos parámetros que tendrá. Me gustaría pasar simplemente en un List de valores, algo así como:

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

estoy creando una instancia de la Mapper donde se define la anotación anterior y decir que es de la siguiente manera:

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

Descubrí que esto no funciona.

org.apache.ibatis.exceptions.PersistenceException:
### Error de base de datos de la consulta. Causa: java.lang.NullPointerException
### El error puede implicar
com.mycompany.MySourceMapper.selectSpecificEmployees-inline
ocurrió ### El error al ajuste de parámetros ### Causa: java.lang.NullPointerException en org.apache.ibatis.exceptions.ExceptionFactory.wrapException (ExceptionFactory.java:8) en org.apache.ibatis.session.defaults.DefaultSqlSession.selectList (DefaultSqlSession.java:77) en org.apache.ibatis.session .defaults.DefaultSqlSession.selectList (DefaultSqlSession.java:69) en org.apache.ibatis.binding.MapperMethod.executeForList (MapperMethod.java:85) en org.apache.ibatis.binding.MapperMethod.execute (M apperMethod.java:65) en org.apache.ibatis.binding.MapperProxy.invoke (MapperProxy.java:35) en $ Proxy23.selectSpecificProductTypes (origen desconocido) en com.mycompany.MySourceMapperDebug.testSelectSpecificEmployees (MySourceMapperDebug.java: 60) en sun.reflect.NativeMethodAccessorImpl.invoke0 (nativo Método) en sun.reflect.NativeMethodAccessorImpl.invoke (origen desconocido) en sun.reflect.DelegatingMethodAccessorImpl.invoke (origen desconocido) en java.lang.reflect.Method .invoke (Fuente desconocida) en junit.framework.TestCase.runTest (TestCase.java:154) en junit.framework.TestCase.runBare (TestCase.java:127) en junit.framework.TestResult $ 1.protect (TestResult .java: 106) en junit.framework.TestResult.runProtected (TestResult.java:124) en junit.framework.TestResult.run (TestResult.java:109) en junit.framework.TestCase.run (TestCase.java:118) en junit.framework.TestSuite.runTest (TestSuite.java:208) en junit.framework.TestSuite.run (TestSuite.java:203) en org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run (JUnit3TestReference.java:130) en org.eclipse.jdt.internal.junit.runner.TestExecution.run (TestExecution.java:38) en org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner. java: 467) en org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests (RemoteTestRunner.java:683) en org.eclipse.jdt.internal.junit. runner.RemoteTestRunner.run (RemoteTestRunner.java:390) en org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main (RemoteTestRunner.java:197) causada por: java.lang.NullPointerException en org.apache .ibatis.type.UnknownTypeHandler.setNonNullParameter (UnknownTypeHandler.java: 21) en org.apache.ibatis.type.BaseTypeHandler.setParameter (BaseTypeHandler.java:23) en org.apache.ibatis.executor.parameter.DefaultParameterHandler.setParameters (DefaultParameterHandler.java:73) en org. apache.ibatis.executor.statement.PreparedStatementHandler.parameterize (PreparedStatementHandler.java:61) en org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize (RoutingStatementHandler.java:43) en org.apache.ibatis.executor. SimpleExecutor.prepareStatement (SimpleExecutor.java:56) en org.apache.ibatis.executor.SimpleExecutor.doQuery (SimpleExecutor.java:40) en org.apache.ibatis.executor.BaseExecutor.queryFromDatabase (BaseExecutor.java:216) en org.apache.ibatis.executor.BaseExecutor.query (BaseExecutor.java: 95) en org.apache.ibatis.executor.CachingExecutor.query (CachingExecutor.java:72) en sun.reflect.NativeMethodAccessorImpl.invoke0 (Nativo Método) en sun.reflect.NativeMethodAccessorImpl.invoke (origen desconocido) en sun.reflect.DelegatingMethodAccessorImpl.invoke (origen desconocido) en java.lang.reflect.Method.invoke (origen desconocido) en org.apache.ibatis.plugin.Invocation.proceed (Invocation.java:31)
.. 36 más

Creo que el problema está en la propia anotación. Parece que sería un requisito bastante común. ¿Debo convertir el List a un String y pasarlo como un parámetro String en lugar de un List<Integer>? ¿O hay alguna otra sintaxis para pasar un List como parámetro a una anotación MyBatis?

+0

Puede usar la configuración basada en xml y la anotación juntos mire esta publicación http://stackoverflow.com/questions/8788250/xml-annotation-based-configuration-for-mybatis/9076435 –

Respuesta

39

Nunca antes he usado anotaciones y MyBatis; Siempre he ido a la ruta del archivo de configuración xml (lo que no implica que haya nada de malo en el uso de anotaciones; simplemente explico que no puedo ayudarlo).

Dicho esto, page 46 from the MyBatis user guide:

foreach

Otra necesidad común para SQL dinámico es la necesidad de iterar sobre una colección , a menudo para construir una condición IN. Por ejemplo:

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

El elemento foreach es muy potente, y le permite especificar una colección , declara variables de artículos e índices que se pueden utilizar en el interior el cuerpo del elemento. También le permite especificar aperturas y cadenas de cierre, y agregar un separador para colocar entre las iteraciones. El elemento es inteligente ya que no anexará accidentalmente separadores adicionales .

+0

gracias esto me ayudó –

+0

Me alegro ¡escucharlo! – Dave

+0

¿Sabe si mybatis puede manejar los límites en las cláusulas IN (como oráculo está limitado a 1000)? Supongo que deberíamos ocuparnos de nosotros mismos –

4

Con un poco de sobrecarga puede usar JAVA para construir una cadena dinámica después de procesar la lista.

  1. definir un Seleccionar proveedor donde se puede construir su consulta dinámica:

    @SelectProvider(type = com.data.sqlprovider.EmployeeSQLBuilder.class, method =  
    "selectSpecificEmployees") 
    List<Employee> selectSpecificEmployees(@Param("employeeIds") List<Integer> 
        employeeIds); 
    
  2. En com.data.sqlprovider.EmployeeSQLBuilder.clase, uso de StringBuilder, generar la consulta

    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

Dado que los posibles employeeIds son enteros, puede salirse con la suya. Sin embargo, si estos fueran cadenas que te tienen un problema si la cadena tiene ningún carácter especial en que – kasdega

+1

concatenación de valores de parámetros en cadena SQL que es malo. Y si los parámetros son cadenas, esta es la receta para la inyección SQL. – blackwizard

0

Si desea utilizar foreach y anotaciones, puede utilizar esta sintaxis:

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

(copiado de que answer)

4

Me enfrento a los mismos problemas exactamente recientemente. Para mi entender, prefiere utilizar Java mapeador en lugar de XML, lo que es lo mismo aquí.

que sigue es lo que estoy haciendo para tratar con él usando: SqlBuilder.

La clase constructor de 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"; 
     } 
    } 

} 

El mapeador:

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

Eso es todo.

El EmployeeSqlBuilder generará dinámicamente la instrucción SQL. Estoy usando una función getSqlConditionCollection para hacer la manipulación lógica. Por supuesto se puede encapsular el getSqlConditionCollection como una función estática en una clase, que es lo que estoy haciendo en el proyecto real, entonces se puede usar fácilmente de otra SqlBuilder.

Cuestiones relacionadas