30

我们只想在 MyBatis 中使用注解;我们真的在努力避免使用 xml。我们正在尝试使用“IN”子句:

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

MyBatis 似乎无法挑选出整数数组并将它们放入结果查询中。它似乎“轻轻失败”,我们没有得到任何结果。

看起来我们可以使用 XML 映射来实现这一点,但我们真的很想避免这种情况。是否有正确的注释语法?

4

9 回答 9

42

我相信答案与这个问题中给出的答案相同。您可以通过执行以下操作在注释中使用 myBatis Dynamic 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 注释。

于 2014-03-25T21:19:29.350 回答
24

我相信这是 jdbc 准备好的语句的细微差别,而不是 MyBatis。这里有一个链接可以解释这个问题并提供各种解决方案。不幸的是,这些解决方案都不适用于您的应用程序,但是,它仍然是一个很好的读物,可以理解准备好的语句在“IN”子句方面的局限性。可以在特定于数据库的方面找到解决方案(可能不是最佳的)。例如,在 postgresql 中,可以使用:

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

“ANY”与“IN”相同,“::int[]”是将参数类型转换为整数数组。输入到语句中的参数应该类似于:

"{1,2,3,4}"
于 2010-08-09T16:35:45.287 回答
18

对这个话题进行了一些研究。

  1. mybatis 的官方解决方案之一是将您的动态 sql 放入@Select("<script>...</script>"). 但是,在java注解中写xml是相当不优雅的。想想这个@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 会将 chars 转换'1,2,3,4'为 number 1
  4. FIND_IN_SET 不适用于 mysql 索引。

查看mybatis动态sql机制,已经实现SqlNode.apply(DynamicContext)。但是,没有注释的@Select 不会<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 one。
  • 解决方案 4:编写自己的 LanguageDriver,它使用模板渲染器编译 SQL,就像 mybatis-velocity 项目一样。这样,你甚至可以集成 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);
于 2015-03-16T11:51:36.743 回答
6

我在我的代码中做了一个小技巧。

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;

它现在可以工作了:) 我希望这会对某人有所帮助。

叶夫根尼

于 2010-12-17T15:54:39.240 回答
4

其他选项可以是

    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);
        }
    }
于 2012-04-26T10:10:34.563 回答
0

在我的项目中,我们已经在使用 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));
    }
}
于 2016-05-23T15:41:27.247 回答
0

在 Oracle 中,我使用Tom Kyte 的标记器的变体来处理未知的列表大小(考虑到 Oracle 对 IN 子句的 1k 限制以及执行多个 IN 来绕过它的恶化)。这适用于 varchar2,但可以针对数字进行定制(或者您可以只依靠 Oracle 知道 '1' = 1 /shudder)。

假设您通过或执行 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;
于 2016-06-24T12:54:53.930 回答
0

您可以使用自定义类型处理程序来执行此操作。例如:

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_SET而不是IN

@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,所以如果它很重要,请测试它

于 2018-05-31T11:51:31.750 回答
0

我已经用postgresql.

    @Update('''
         UPDATE sample_table 
         SET start = null, finish = null
         WHERE id=ANY(#{id});
            ''')
    int resetData(@Param("id") String[] id)

ANYIN.

上面的代码正在使用groovy,但可以java通过将单引号替换为双引号来转换。

于 2021-04-19T11:38:41.700 回答