Question

Nous aimerions utiliser les annotations avec seulement mybatis; nous essayons vraiment d'éviter xml. Nous essayons d'utiliser une clause "IN":

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

mybatis ne semble pas en mesure de choisir le tableau de ints et de mettre ceux dans la requête résultante. Il semble « échouons doucement » et nous obtenons aucun résultat en arrière.

Il semble que nous pourrions accomplir cela en utilisant des applications XML, mais nous aimerions vraiment éviter cela. Y at-il une syntaxe correcte d'annotation pour cela?

Était-ce utile?

La solution

Je crois que c'est une nuance de déclarations préparées et non de jdbc mybatis. Il existe un lien qui explique ce problème et propose diverses solutions. Malheureusement, aucune de ces solutions sont viables pour votre application, cependant, il est encore une bonne lecture pour comprendre les limites des déclarations préparées en ce qui concerne une clause « IN ». Une solution (peut-être sous-optimale) se trouve sur le côté DB spécifique des choses. Par exemple, dans postgresql, on peut utiliser:

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

"ANY" est le même que "IN" et ":: int []" est de type coulée l'argument dans un tableau de ints. L'argument qui est introduit dans la déclaration devrait ressembler à:

"{1,2,3,4}"

Autres conseils

Je crois que la réponse est la même que celle donnée dans cette question. Vous pouvez utiliser mybatis SQL dynamique dans vos annotations en procédant comme suit:

@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'élément <script> permet l'analyse dynamique SQL et l'exécution de l'annotation. Il doit être tout premier contenu de la chaîne de requête. Rien ne doit être en face d'elle, même pas l'espace blanc.

Notez que les variables que vous pouvez utiliser dans les différentes balises de script XML suivent les mêmes conventions de nommage que les requêtes régulières, donc si vous voulez faire référence à vos arguments de méthode en utilisant des noms autres que « param1 », « param2 », etc. .. vous devez préfixer chaque argument avec une annotation @param.

avait quelques recherches sur ce sujet.

  1. l'une des solution officielle de mybatis est de mettre votre sql dynamique @Select("<script>...</script>"). Cependant, l'écriture xml dans l'annotation java est tout à fait disgracieuse. pensez à ce @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider fonctionne très bien. Mais il est un peu compliqué à lire.
  3. PreparedStatement permet pas vous définissez la liste des nombres entiers. pstm.setString(index, "1,2,3,4") permettra à votre SQL comme celui-ci select name from sometable where id in ('1,2,3,4'). Mysql vous permet de convertir les caractères '1,2,3,4' au numéro 1.
  4. FIND_IN_SET ne fonctionne pas avec l'index de MySQL.

Regardez pour mybatis mécanisme sql dynamique, il a été mis en œuvre par SqlNode.apply(DynamicContext). Cependant, sans @Select annotation <script></script> ne passera pas paramètre via DynamicContext

voir aussi

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

  • Solution 1: Utilisez @SelectProvider
  • Solution 2: Extension LanguageDriver qui sera toujours à la compilation sql DynamicSqlSource. Cependant, vous avez encore à écrire \" partout.
  • Solution 3: Étendre LanguageDriver qui peut convertir votre propre grammaire à mybatis un.
  • Solution 4: Écrivez votre propre LanguageDriver qui compile SQL avec un certain modèle de moteur de rendu, tout comme projet mybatis-vitesse fait. De cette façon, vous pouvez même intégrer groovy.

Mon projet solution de prendre 3 et voici le 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);
    }
}

Et l'utilisation:

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

Je l'ai fait un petit truc dans mon 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);
}

Et je cette MyHandler dans 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;

Il fonctionne maintenant :) J'espère que ce quelqu'un aide.

Evgeny

Autre option peut être

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

Dans mon projet, nous utilisons déjà Google goyave, donc un raccourci rapide est.

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

Dans Oracle, j'utilise une variante de tokenizer Tom Kyte pour gérer la taille des inconnus liste (limite donnée d'Oracle 1k sur une clause IN et l'aggravation de faire plusieurs pour se déplacer iNs elle). Ceci est pour varchar2, mais il peut être adapté pour les nombres (ou vous pouvez simplement compter sur Oracle sachant que « 1 » = 1 / frisson).

En supposant que vous passez ou effectuer mybatis incantations pour obtenir ids en tant que chaîne, à l'utiliser:

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

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

Vous pouvez utiliser un gestionnaire de type personnalisé pour le faire. Par exemple:

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

Enregistrez le gestionnaire de type suivant dans votre mybatis config (ou préciser dans l'annotation):

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
}

Vous pouvez ensuite les utiliser comme ceci

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

Cependant, cette volonté pas travail pour MySQL, car le connecteur MySQL ne setArray() pas de support pour les instructions préparées.

Une solution possible pour MySQL est d'utiliser FIND_IN_SET au lieu de IN:

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

Et votre gestionnaire de type devient:

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

Note: Je ne sais pas les performances de FIND_IN_SET, testez donc cela s'il est important

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top