IN クエリに iBatis (myBatis) でアノテーションを使用するにはどうすればよいですか?

StackOverflow https://stackoverflow.com/questions/3428742

質問

MyBatis では注釈のみを使用したいと考えています。私たちは実際に XML を避けようとしています。「IN」句を使用しようとしています。

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

MyBatis は、int の配列を選択して、結果のクエリにそれらを入れることができないようです。「緩やかに失敗」しているように見え、結果は返されません。

XML マッピングを使用してこれを実現できるように見えますが、それは避けたいと考えています。これに対する正しい注釈構文はありますか?

役に立ちましたか?

解決

私は、これは、JDBCのプリペアドステートメントではなくMyBatisののニュアンスであると考えています。この問題と申し出様々な解決策を説明しrel="noreferrer">ここをhref="http://www.javaranch.com/journal/200510/Journal200510.jsp#a2"

"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"
「ANY」「IN」と同じである「:: INT []」intの配列に引数をキャストするタイプです。声明に供給される引数は次のようになります。

"{1,2,3,4}"

他のヒント

答えはで与えられたものと同じだと思います この質問. 。次の手順を実行すると、注釈で myBatis 動的 SQL を使用できます。

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

<script> 要素により、注釈の動的 SQL 解析と実行が可能になります。これはクエリ文字列の最初のコンテンツである必要があります。その前には空白も含めて何もあってはならない。

さまざまな XML スクリプト タグで使用できる変数は、通常のクエリと同じ命名規則に従っていることに注意してください。したがって、「param1」、「param2」など以外の名前を使用してメソッドの引数を参照したい場合は...各引数の前に @Param アノテーションを付ける必要があります。

た研究である.

  1. のオフィシャル溶液からmybatisはパロアルトネットワークスはsqlに @Select("<script>...</script>").しかし、文書のxmlにjavaの注釈がなかなかungraceful.このことを考えて @Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
  2. @SelectProvider に働きます。でもちょっと複雑です。
  3. PreparedStatementを許さないセットリストの整数です。 pstm.setString(index, "1,2,3,4") うおSQLこのような select name from sometable where id in ('1,2,3,4').Mysqlに変換し文字数 '1,2,3,4'1.
  4. FIND_IN_SETな作品mysql。

見にmybatis動的sql機構で実施している SqlNode.apply(DynamicContext).しかし、@を選択なし <script></script> 注釈のないパスパラメータ経由 DynamicContext

参照

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

なので、

  • 解決法1:利用@SelectProvider
  • 解決法2:延長LanguageDriverこれからも常にコンパイルするsql DynamicSqlSource.しかし、まだ書 \" ます。
  • 解決3:延長LanguageDriverの変換に関するご自身の文法にmybatisます。
  • Solution4:書自LanguageDriverるSQLコンパイルとテンプレートのレンダリングのようにmybatis速度プロジェクト。このように、できる統合groovy.

私のプロジェクトを取溶液に3-このコード:

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

の使用量:

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

私は自分のコード内の小さなトリックを作りました。

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

そして、私はSqlMapperでこのMyHandlerというを使用します:

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

これが動作するようになりました:) 私は、この意志のヘルプ誰かを願っています。

エフゲニー

他のオプションが可能

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

迅速なショートカットがあるので、私のプロジェクトでは、我々はすでに、Googleのグアバを使用しています。

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

Oracle では、次の亜種を使用します。 Tom Kyte のトークナイザー 不明なリスト サイズを処理するため (Oracle の IN 句に対する 1k 制限と、それを回避するために複数の IN を実行することの煩雑さを考慮して)。これは varchar2 用ですが、数値に合わせて調整することもできます (または、「1」 = 1 /shudder を認識している Oracle に依存することもできます)。

myBatis の呪文を渡すか実行して取得すると仮定します。 ids 文字列として使用するには、次のようにします。

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

コード:

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;

これを行うには、カスタム型ハンドラを使用することができます。たとえばます:

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

あなたのMyBatisの設定では、次のタイプのハンドラを登録(またはあなたの注釈で指定):

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
}

あなたは、このようにそれらを使用することができます。

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

しかし、この意志のMySQL用の のない仕事、MySQLのコネクタが準備された文のためsetArray()をサポートしていないため。

MySQLのための可能な回避策は代わりにFIND_IN_SETINを使用することです。

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

そして、あなたのタイプのハンドラになります:

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

注:私は、FIND_IN_SETのパフォーマンスを知らないので、これをテストすることが重要である場合は、

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top