MyBatisではアノテーションのみを使用します。私たちは本当にxmlを避けようとしています。 「IN」句を使用しようとしています。
@Select("SELECT * FROM blog WHERE id IN (#{ids})")
List<Blog> selectBlogs(int[] ids);
MyBatisは、intの配列を取り出して、それらを結果のクエリに入れることができないようです。 「ソフトに失敗する」ようで、結果は返されません。
XMLマッピングを使用してこれを達成できるように見えますが、それは本当に避けたいです。これに対する正しい注釈構文はありますか?
これは、JDBCが準備したステートメントのニュアンスであり、MyBatisではないと思います。この問題を説明し、さまざまな解決策を提供するリンク here があります。残念ながら、これらの解決策はどれもアプリケーションに実行可能ではありませんが、 "IN"句に関する準備されたステートメントの制限を理解することは、良い読み物です。ソリューション(おそらく最適ではない)は、DB固有の側面にあります。たとえば、postgresqlでは、次のように使用できます。
"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"
「ANY」は「IN」と同じであり、「:: 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アノテーションを付ける必要があります。
このトピックに関するいくつかの調査がありました。
@Select("<script>...</script>")
に配置することです。ただし、Javaアノテーションでxmlを記述することは非常に不快です。これについて考えてください@Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
@SelectProvider
_は正常に動作します。しかし、読むのは少し複雑です。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
_に変換します。Mybatis動的SQLメカニズムを調べてください。これはSqlNode.apply(DynamicContext)
によって実装されています。ただし、_<script></script>
_アノテーションなしの@Selectは、DynamicContext
を介してパラメーターを渡しません。
も参照
org.Apache.ibatis.scripting.xmltags.XMLLanguageDriver
_org.Apache.ibatis.scripting.xmltags.DynamicSqlSource
_org.Apache.ibatis.scripting.xmltags.RawSqlSource
_そう、
DynamicSqlSource
にコンパイルするLanguageDriverを拡張します。ただし、どこでも_\"
_を記述する必要があります。私のプロジェクトはソリューション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);
}
そして、私はこのMyHandlerを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;
それは今動作します:)私はこれが誰かを助けることを望みます。
エフゲニー
他のオプションは
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);
}
}
Oracleでは、不明なリストサイズを処理するために、 Tom Kyteのトークナイザー のバリアントを使用します(IN句に対するOracleの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コネクタは準備されたステートメントのsetArray()
をサポートしていないため、これはnotがMySQLで機能します。
MySQLの可能な回避策は、IN
の代わりに_FIND_IN_SET
_を使用することです。
_@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
_のパフォーマンスがわからないので、重要な場合はテストしてください
私のプロジェクトでは、すでにGoogle Guavaを使用しているので、簡単なショートカットを使用します。
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));
}
}