Domanda

Vorremmo utilizzare solo le annotazioni con MyBatis; stiamo davvero cercando di evitare xml. Stiamo cercando di utilizzare una clausola "IN":

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

MyBatis non sembra in grado di individuare la matrice di int e mettere quelli nella query risultante. Sembra "riusciamo a bassa voce" e otteniamo alcun risultato indietro.

Sembra che siamo riusciti a realizzare questo utilizzando mappature XML, ma saremmo davvero come per evitare che. C'è una sintassi di annotazione corretta per questo?

È stato utile?

Soluzione

Credo che questa è una sfumatura di istruzioni preparate JDBC e non MyBatis. C'è un link qui che spiega questo problema e offre diverse soluzioni. Purtroppo, nessuna di queste soluzioni sono vitali per la vostra applicazione, tuttavia, la sua ancora una buona lettura per capire i limiti di dichiarazioni preparate per quanto riguarda una clausola "IN". Una soluzione (forse non ottimale) può essere trovato sul lato DB-specifica delle cose. Per esempio, in PostgreSQL, si potrebbe usare:

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

"ANY" è la stessa di "IN" e ":: int []" è il tipo di getto l'argomento in un array di int. L'argomento che viene immessa nella dichiarazione dovrebbe essere simile a:

"{1,2,3,4}"

Altri suggerimenti

Credo che la risposta è la stessa è dato in questa domanda . È possibile utilizzare myBatis SQL dinamico nelle vostre annotazioni facendo quanto segue:

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

L'elemento <script> consente parsing SQL dinamico ed esecuzione per l'annotazione. Deve essere molto prima il contenuto della stringa di query. Nulla deve essere di fronte ad essa, nemmeno lo spazio bianco.

Si noti che le variabili che è possibile utilizzare nei vari tag di script XML seguono le stesse convenzioni di denominazione le query regolari, quindi se si vuole fare riferimento al metodo di argomenti utilizzando nomi diversi "param1", "param2", etc. .. è necessario precedere ogni argomento con un'annotazione @ param.

Ha avuto qualche ricerca su questo argomento.

  1. una delle soluzione ufficiale da mybatis è quello di mettere il vostro SQL dinamico @Select("<script>...</script>"). Tuttavia, la scrittura di XML in annotazione è piuttosto sgraziata. pensare a questo @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider funziona bene. Ma è un po 'complicato da leggere.
  3. PreparedStatement non consente di impostare l'elenco dei numeri interi. pstm.setString(index, "1,2,3,4") permetterai che il tuo SQL come questo select name from sometable where id in ('1,2,3,4'). MySQL convertire caratteri '1,2,3,4' al numero di 1.
  4. FIND_IN_SET non funziona con indice di mysql.

Cerca in per mybatis meccanismo di SQL dinamico, è stato implementato da SqlNode.apply(DynamicContext). Tuttavia, @Select senza <script></script> annotazione non passerà ai parametri attraverso DynamicContext

vedi anche

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

  • Soluzione 1: Usare @SelectProvider
  • Soluzione 2: Estendere LanguageDriver che sarà sempre di compilazione sql per DynamicSqlSource. Tuttavia, si devono ancora \" scrittura ovunque.
  • Soluzione 3: Estendere LanguageDriver che può convertire il proprio grammatica mybatis uno.
  • Soluzione 4: Scrivi la tua LanguageDriver che compilare SQL con qualche modello di rendering, proprio come progetto mybatis-velocità fa. In questo modo, si può anche integrare i groove.

Il mio progetto take soluzione 3 ed ecco il codice:

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 l'utilizzo:

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

Ho fatto un piccolo trucco nel mio codice.

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 ho usato questo MyHandler in 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;

E 'ora funziona :) Spero che questo vi aiuterà qualcuno.

Evgeny

Altri opzione può essere

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

Nel mio progetto, stiamo già utilizzando Google Guava, quindi un collegamento rapido è.

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

In Oracle, io uso una variante di di Tom Kyte tokenizer di gestire formati lista sconosciuti (data limite di Oracle 1k su una clausola iN e l'aggravamento di fare più in per aggirare l'ostacolo). Questo è per VARCHAR2, ma può essere personalizzato per i numeri (o si può fare affidamento solo su Oracle sapendo che '1' = 1 / brivido).

Supponendo che si passa o eseguire myBatis incantesimi per ottenere ids come una stringa, per usarlo:

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

Il codice:

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;

Si potrebbe utilizzare un gestore di tipo personalizzato per fare questo. Ad esempio:

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

Registrare il seguente gestore digitare il MyBatis config (o specificare nella vostra annotazione):

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
}

È quindi possibile utilizzare come questo

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

Tuttavia, questa volontà non il lavoro per MySQL, in quanto il connettore MySQL non setArray() non il supporto per le istruzioni preparate.

Una soluzione possibile per MySQL è quello di utilizzare al posto di FIND_IN_SET IN:

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

E il vostro gestore di tipo diventa:

@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: Non so le prestazioni di FIND_IN_SET, in modo da provare questa se è importante

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top