Frage

Wir möchten nur Anmerkungen mit Mybatis verwenden; Wir versuchen wirklich xml zu vermeiden. Wir versuchen, eine „IN“ Klausel zu verwenden:

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

Mybatis ist nicht in der Lage scheint das Array von ints herauszusuchen und diejenigen, in die resultierende Abfrage setzen. Es scheint „fail leise“ und wir keine Ergebnisse zurück.

Es sieht aus wie wir diese mit Hilfe von XML-Mappings erreichen können, aber wir sollten wirklich so zu vermeiden. Gibt es eine korrekte Annotation Syntax für das?

War es hilfreich?

Lösung

Ich glaube, das ist eine Nuance von Prepared Statements der jdbc und nicht Mybatis. Es gibt einen Link hier , die dieses Problem und bietet verschiedene Lösungen erläutert. Leider ist keine dieser Lösungen für Ihre Anwendung lebensfähig, aber es ist immer noch ein gut, die Grenzen der vorbereiteten Anweisungen in Bezug auf eine „IN“ Klausel zu verstehen lesen. Eine Lösung (vielleicht suboptimal) auf der DB-spezifische Seite der Dinge zu finden. Zum Beispiel in postgresql, könnte man verwenden:

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

"ANY" ist die gleiche wie "IN" und ":: int []" ist der Typ das Argument in ein Array von ints Gießen. Das Argument, dass in der Erklärung zugeführt wird, sollte in etwa so aussehen:

"{1,2,3,4}"

Andere Tipps

Ich glaube, die Antwort ist das gleiche wie in diese Frage . Sie können, indem Sie die folgende Mybatis dynamische SQL in Ihren Anmerkungen verwenden:

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

Das <script> Element ermöglicht die dynamische SQL-Parsing und Ausführung für die Anmerkung. Es muss sehr erster Inhalt des Query-String sein. Es darf nichts vor ihm sein, nicht einmal Leerraum.

Beachten Sie, dass die Variablen, die Sie in den verschiedenen XML-Script-Tags folgen den gleichen Namenskonventionen wie regelmäßige Abfragen verwenden können, wenn Sie also auf Ihre Methodenargumente beziehen möchten Namen anders als „param1“ mit „param2“ usw. .. Sie benötigen jedes Argument mit einer @param Anmerkung Präfix.

Wir hatten einige der Forschung zu diesem Thema.

  1. einer der offiziellen Lösung von Mybatis ist Ihre dynamische SQL in @Select("<script>...</script>") zu setzen. Allerdings ist XML in Java Annotation Schreiben ziemlich plump. denken über dieses @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider funktioniert gut. Aber es ist ein wenig kompliziert zu lesen.
  3. PreparedStatement erlauben nicht gesetzt Sie eine Liste von Integer. pstm.setString(index, "1,2,3,4") Ihre SQL wie diese select name from sometable where id in ('1,2,3,4') lassen. Mysql konvertiert Zeichen '1,2,3,4' Nummer 1.
  4. FIND_IN_SET Sie funktioniert nicht mit mysql-Index.

Schauen Sie in zu Mybatis dynamischen SQL-Mechanismus wird durch SqlNode.apply(DynamicContext) umgesetzt. Allerdings wird @Select ohne <script></script> Anmerkung nicht Parameter übergeben über DynamicContext

siehe auch

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

So

  • Lösung 1: Verwenden Sie @SelectProvider
  • Lösung 2: Erweitern LanguageDriver die wird immer Kompilierung SQL DynamicSqlSource. Aber man noch überall zu schreiben \" hat.
  • Lösung 3: Erweitern LanguageDriver die eigene Grammatik zu Mybatis umwandeln kann.
  • Lösung 4: Schreiben Sie Ihre eigene LanguageDriver die SQL mit einigen Template-Renderer zu kompilieren, wie Mybatis-Geschwindigkeit Projekt der Fall ist. Auf diese Weise können Sie auch groovy integrieren.

Ihr Projekt nehmen Lösung 3 und hier ist der Code:

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

Und die Nutzung:

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

Ich habe einen kleinen Trick in meinem Code.

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

Und ich verwenden, um diesen 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;

Es funktioniert jetzt :) Ich hoffe, dass dies dazu beitragen wird jemand.

Evgeny

Andere Option sein kann

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

In meinem Projekt sind wir bereits mit Google Guava, so dass eine schnelle Abkürzung ist.

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, verwende ich eine Variante von Tom Kyte des tokenizer unbekannt Liste Größen zu handhaben (bei Oracle 1k Grenze auf einer IN-Klausel und die Verschlimmerung von mehrer in tut es zu umgehen). Dies ist für varchar2, aber es kann für Zahlen angepasst werden (oder Sie können vertrauen nur auf Oracle zu wissen, dass ‚1‘ = 1 / Schauder).

Angenommen, Sie übergeben oder führen Mybatis Beschwörungen ids als String zu bekommen, es zu benutzen:

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

Der Code:

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;

Sie können einen benutzerdefinierten Typ-Handler verwenden, um dies zu tun. Zum Beispiel:

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

Registrieren Sie die folgenden Typ-Handler in Ihrer Mybatis config (oder in der Anmerkung angeben):

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
}

Sie können sie dann wie folgt verwenden

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

Dies wird jedoch nicht Arbeit für MySQL, da der MySQL-Anschluss nicht unterstützt setArray() für vorbereitete Anweisungen.

Eine mögliche Abhilfe für MySQL ist FIND_IN_SET statt IN zu verwenden:

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

Und Ihr Typ-Handler wird:

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

Hinweis: Ich weiß nicht, die Leistung von FIND_IN_SET, dies so testen, ob es wichtig ist,

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top