Pregunta

Nos gustaría usar sólo las anotaciones con MyBatis; realmente estamos tratando de evitar XML. Estamos tratando de utilizar una cláusula de "IN":

@Select("SELECT * FROM blog WHERE id IN (#{ids})") 
List<Blog> selectBlogs(int[] ids); 

MyBatis no parece capaz de seleccionar la matriz de enteros y poner en los que la consulta resultante. Parece que "fallamos en voz baja" y no conseguimos resultados de nuevo.

Parece que podríamos lograr esto utilizando mapeos XML, pero nos gusta mucho para evitar eso. ¿Existe una sintaxis correcta anotación para esto?

¿Fue útil?

Solución

creo que esto es un matiz de declaraciones preparadas de JDBC y no MyBatis. Hay un enlace aquí que explica este problema y ofrece diversas soluciones. Por desgracia, ninguna de estas soluciones son viables para su aplicación, sin embargo, su todavía una buena lectura para comprender las limitaciones de las declaraciones preparadas con respecto a una cláusula de "IN". Una solución (tal vez subóptima) se puede encontrar en el lado DB-específica de las cosas. Por ejemplo, en postgresql, se podría utilizar:

"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"

"ANY" es el mismo que "IN" y ":: int []" es de tipo fundición el argumento en una matriz de enteros. El argumento que se introduce en la declaración debe ser algo como:

"{1,2,3,4}"

Otros consejos

Creo que la respuesta es la misma que se da en la esta pregunta . Puede utilizar mybatis SQL dinámico en sus anotaciones haciendo lo siguiente:

@Select({"<script>",
         "SELECT *", 
         "FROM blog",
         "WHERE id IN", 
           "<foreach item='item' index='index' collection='list'",
             "open='(' separator=',' close=')'>",
             "#{item}",
           "</foreach>",
         "</script>"}) 
List<Blog> selectBlogs(@Param("list") int[] ids);

El elemento <script> permite el análisis sintáctico SQL dinámico y ejecución para la anotación. Debe ser muy primer contenido de la cadena de consulta. Nada debe estar delante de él, ni siquiera espacio en blanco.

Tenga en cuenta que las variables que se pueden utilizar en las distintas etiquetas de script XML siguen las mismas convenciones de nomenclatura como consultas regulares, por lo que si se desea hacer referencia a los argumentos del método utilizando nombres distintos "param1", "param2", etc. .. que necesita para prefijar cada argumento con una anotación @Param.

Tenía un poco de investigación sobre este tema.

  1. una de solución oficial de mybatis es poner el SQL dinámico en @Select("<script>...</script>"). Sin embargo, escribir xml en la anotación de Java es bastante poco elegante. pensar en esto @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider funciona bien. Pero es un poco complicado de leer.
  3. PreparedStatement no permite configurar la lista de número entero. pstm.setString(index, "1,2,3,4") le permitirá a su SQL como esto select name from sometable where id in ('1,2,3,4'). MySQL convertir caracteres '1,2,3,4' al número 1.
  4. FIND_IN_SET no trabaja con índice de MySQL.

Buscar en mybatis al mecanismo de SQL dinámico, se ha implementado por SqlNode.apply(DynamicContext). Sin embargo, @Select sin anotación <script></script> no pasará parámetro mediante DynamicContext

consulta

  • org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
  • org.apache.ibatis.scripting.xmltags.DynamicSqlSource
  • org.apache.ibatis.scripting.xmltags.RawSqlSource

Por lo tanto,

  • Solución 1: Uso @SelectProvider
  • Solución 2: Extender LanguageDriver que siempre compilación SQL para DynamicSqlSource. Sin embargo, usted todavía tiene que \" escritura en todas partes.
  • Solución 3: Extender LanguageDriver que puede convertir su propia gramática a mybatis uno.
  • Solución 4: Escriba su propia LanguageDriver el cual compila SQL con algún procesador de plantilla, al igual que el proyecto mybatis velocidad lo hace. De esta manera, se puede incluso integrar maravilloso.

Mi proyecto de puesta en solución 3 y aquí está el código:

public class MybatisExtendedLanguageDriver extends XMLLanguageDriver 
                                           implements LanguageDriver {
    private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        Matcher matcher = inPattern.matcher(script);
        if (matcher.find()) {
            script = matcher.replaceAll("(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
        }
        script = "<script>" + script + "</script>";
        return super.createSqlSource(configuration, script, parameterType);
    }
}

Y el uso:

@Lang(MybatisExtendedLanguageDriver.class)
@Select("SELECT " + COLUMNS + " FROM sometable where id IN (#{ids})")
List<SomeItem> loadByIds(@Param("ids") List<Integer> ids);

He hecho un pequeño truco en mi código.

public class MyHandler implements TypeHandler {

public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
    Integer[] arrParam = (Integer[]) parameter;
    String inString = "";
    for(Integer element : arrParam){
      inString = "," + element;
    }
    inString = inString.substring(1);        
    ps.setString(i,inString);
}

Y he utilizado este MyHandler en SqlMapper:

    @Select("select id from tmo where id_parent in (#{ids, typeHandler=ru.transsys.test.MyHandler})")
public List<Double> getSubObjects(@Param("ids") Integer[] ids) throws SQLException;

Se trabaja ahora :) Espero que esto ayude a alguien.

Evgeny

Otra opción puede ser

    public class Test
    {
        @SuppressWarnings("unchecked")
        public static String getTestQuery(Map<String, Object> params)
        {

            List<String> idList = (List<String>) params.get("idList");

            StringBuilder sql = new StringBuilder();

            sql.append("SELECT * FROM blog WHERE id in (");
            for (String id : idList)
            {
                if (idList.indexOf(id) > 0)
                    sql.append(",");

                sql.append("'").append(id).append("'");
            }
            sql.append(")");

            return sql.toString();
        }

        public interface TestMapper
        {
            @SelectProvider(type = Test.class, method = "getTestQuery")
List<Blog> selectBlogs(@Param("idList") int[] ids);
        }
    }

En mi proyecto, ya estamos usando Google guayaba, por lo que es un atajo rápido.

public class ListTypeHandler implements TypeHandler {

    @Override
    public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, Joiner.on(",").join((Collection) parameter));
    }
}

En Oracle, utilizo una variante de tokenizer de Tom Kyte para manejar tamaños de lista desconocidos (dado límite de Oracle 1k en una cláusula IN y el agravamiento de hacer múltiples ins para conseguir alrededor de él). Esto es para VARCHAR2, pero puede ser adaptado para los números (o lo que podría depender de Oracle sabiendo que '1' = 1 / temblor).

Suponiendo que pase o realizar mybatis encantamientos para conseguir ids como una cadena, se usa:

select @Select("SELECT * FROM blog WHERE id IN (select * from table(string_tokenizer(#{ids}))")

El código:

create or replace function string_tokenizer(p_string in varchar2, p_separator in varchar2 := ',') return sys.dbms_debug_vc2coll is
    return_value SYS.DBMS_DEBUG_VC2COLL;
    pattern varchar2(250);
begin
    pattern := '[^(''' || p_separator || ''')]+' ;

    select
        trim(regexp_substr(p_string, pattern, 1, level)) token
    bulk collect into
        return_value
    from
        dual
    where
        regexp_substr(p_string, pattern, 1, level) is not null
    connect by
        regexp_instr(p_string, pattern, 1, level) > 0;

    return return_value;
end string_tokenizer;

Se puede usar un controlador de tipo personalizado para hacer esto. Por ejemplo:

public class InClauseParams extends ArrayList<String> {
   //...
   // marker class for easier type handling, and avoid potential conflict with other list handlers
}

Registrar el siguiente controlador de tipo en su MyBatis config (o especificar en su anotación):

public class InClauseTypeHandler extends BaseTypeHandler<InClauseParams> {

    @Override
    public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {

        // MySQL driver does not support this :/
        Array array = ps.getConnection().createArrayOf( "VARCHAR", parameter.toArray() );
        ps.setArray( i, array );
    }
    // other required methods omitted for brevity, just add a NOOP implementation
}

A continuación, puede utilizarlos como esto

@Select("SELECT * FROM foo WHERE id IN (#{list})"
List<Bar> select(@Param("list") InClauseParams params)

Sin embargo, esta voluntad no trabajo para MySQL, debido a que el conector de MySQL hace setArray() no soporte para comandos preparados.

Una posible solución para MySQL es utilizar FIND_IN_SET en lugar de IN:

@Select("SELECT * FROM foo WHERE FIND_IN_SET(id, #{list}) > 0")
List<Bar> select(@Param("list") InClauseParams params)

Y el controlador de tipo se convierte en:

@Override
    public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {

        // note: using Guava Joiner! 
        ps.setString( i, Joiner.on( ',' ).join( parameter ) );
    }

Nota: no sé el rendimiento de FIND_IN_SET, por lo que esta prueba si es importante

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top