Pergunta

Nós gostaríamos de usar, apenas as anotações com o MyBatis;nós estamos realmente tentando evitar xml.Estamos tentando usar um "NA" cláusula:

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

MyBatis não parece capaz de escolher o array de ints e colocar em consulta resultante.Parece-falha "baixinho" e nós não obter resultados.

Parece que nós poderíamos fazer isso usando mapeamentos XML, mas nós realmente gostaria de evitar.Há uma correta anotação de sintaxe para isso?

Foi útil?

Solução

Acredito que isso é uma nuance das declarações preparadas da JDBC e não a Mybatis. Há um link aqui Isso explica esse problema e oferece várias soluções. Infelizmente, nenhuma dessas soluções é viável para sua aplicação, no entanto, ainda é uma boa leitura para entender as limitações das declarações preparadas com relação a uma cláusula "in". Uma solução (talvez abaixo do ideal) pode ser encontrada no lado específico do banco de dados. Por exemplo, no PostgreSQL, pode -se usar:

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

"Qualquer" é o mesmo que "em" e ":: int [] é o tipo de lançamento do argumento em uma matriz de ints. O argumento que é alimentado na declaração deve parecer algo como:

"{1,2,3,4}"

Outras dicas

Creio que a resposta é a mesma que é dada em esta pergunta.Você pode usar o myBatis SQL Dinâmico em suas anotações, fazendo o seguinte:

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

O <script> elemento permite que o SQL dinâmico análise e execução para a anotação.Ele deve ser o primeiro conteúdo da cadeia de caracteres de consulta.Nada deve ser em frente a ele, nem mesmo o espaço em branco.

Note que as variáveis que você pode usar em várias script XML tags seguem as mesmas convenções de nomenclatura como consultas regulares, então, se você quiser se referir a seu método de argumentos usando nomes de terceiros "param1", "param2", etc...você precisa prefixar cada argumento com um @Param de anotação.

Tive algumas pesquisas sobre esse tópico.

  1. Uma solução oficial da Mybatis é colocar seu sql dinâmico em @Select("<script>...</script>"). No entanto, escrever XML na anotação de Java é bastante desagradável. pense sobre isso @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider funciona bem. Mas é um pouco complicado de ler.
  3. Preparado, não permite que você defina a lista de inteiros. pstm.setString(index, "1,2,3,4") vai deixar seu SQL assim select name from sometable where id in ('1,2,3,4'). Mysql vai converter chars '1,2,3,4' enumerar 1.
  4. Find_in_set não funciona com o índice MySQL.

Olhe para Mybatis Dynamic SQL Mecanism, foi implementado por SqlNode.apply(DynamicContext). No entanto, @Select sem <script></script> Anotação não passará o parâmetro via DynamicContext

Veja também

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

Então,

  • Solução 1: Use @selectProvider
  • Solução 2: estender o RanguagedRiver que sempre compilará o SQL para DynamicSqlSource. No entanto, você ainda tem que escrever \" em toda parte.
  • Solução 3: Estenda o RanguagedRiver que pode converter sua própria gramática em Mybatis.
  • Solução 4: Escreva seu próprio RanguagedRiver, que compila SQL com algum renderizador de modelo, assim como o projeto Mybatis-Velocity. Dessa forma, você pode até integrar o Groovy.

Meu projeto Take Solution 3 e aqui está o 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);
    }
}

E o uso:

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

Fiz um pequeno truque no meu 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);
}

E eu usei este MyHandler no SQLMAPPPER:

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

Funciona agora :) Espero que isso ajude alguém.

Evgeny

Outra opção pode 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);
        }
    }

No meu projeto, já estamos usando o Google Guava, então um atalho 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));
    }
}

No Oracle, eu uso uma variante de TOKenizer de Tom Kyte Para lidar com tamanhos de lista desconhecidos (dado o limite de 1k da Oracle em uma cláusula in e o agravamento de fazer vários INs para contorná -lo). Isto é para VARCHAR2, mas pode ser adaptado para números (ou você pode confiar no Oracle, sabendo que '1' = 1 /shudder).

Supondo que você passe ou execute encantamentos mybatis para obter ids Como string, para usá -la:

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

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

Você pode usar um manipulador de tipo personalizado para fazer isso. Por exemplo:

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

Registre o seguinte manipulador de tipo em sua configuração mybatis (ou especifique em sua anotação):

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
}

Você pode então usá -los assim

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

No entanto, isso vai não Trabalhe para MySQL, porque o conector MySQL não suporta setArray() para declarações preparadas.

Uma possível solução alternativa para o MySQL é usar FIND_IN_SET ao invés de IN:

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

E seu manipulador de tipos se torna:

@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: Eu não conheço o desempenho de FIND_IN_SET, então teste isso se for importante

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top