2010-08-07 113 views
20

我们只想对MyBatis使用注释;我们真的试图避免xml。我们正在尝试使用“IN”的条款:如何使用iBatis注解(myBatis)进行IN查询?

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

MyBatis的似乎不能够挑选出整数数组,并把那些到生成的查询。它似乎“轻轻地失败”,我们没有得到任何结果。

看起来我们可以使用XML映射完成此操作,但我们真的很想避免这种情况。有没有一个正确的注释语法?

+0

普通SQL需要动态SQL使用该代表的变量逗号分隔的值列表。 – 2010-08-07 01:27:56

+0

@OMG小马:我很抱歉,我不确定你想说什么?如果我要把你的智慧应用到这个问题上,我的解决方案会是什么样子? – dirtyvagabond 2010-08-07 01:37:17

+0

我从来没有使用过iBatis,但是在任何事情发生之前你能创建一个字符串(包括变量内容)的SQL语句吗?这就是所有动态SQL实际上是... – 2010-08-07 01:42:37

回答

15

我相信这是jdbc准备好的语句而不是MyBatis的细微差别。有一个链接here解释这个问题,并提供各种解决方案。不幸的是,这些解决方案都不适用于您的应用程序,但是,它仍然是理解预备语句与“IN”条款有关的限制的理想选择。一个解决方案(可能不是最理想的)可以在数据库特定的一面找到。例如,PostgreSQL中,可以使用:

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

“ANY”是相同的“IN”和“:: INT []”是类型铸造参数转换为整数的数组。这个陈述应该看起来像这样:

"{1,2,3,4}" 
+0

JavaRanch链接提出了一个有趣的想法,即将数组拆分为多个块并执行批处理。这不是postgres的具体情况,可以在类似Handler的@ pevgen的建议下在iBatis中实现。 – AngerClown 2010-12-17 18:30:51

+0

在MySQL中,使用以下查询,将“blogIds”作为字符串传递,ID以逗号分隔:“SELECT * FROM blog WHERE FIND_IN_SET(id,#{blogIds})<> 0” – 2012-02-09 04:52:10

+0

这是一个很棒的第一步,但没有为我工作。最后,我必须为ArrayList编写一个类型处理程序(使用connection.createArrayOf()),然后在:: int []之前直接在{}部分中引用类型处理程序。但是,感谢好的领导。 – Blamkin86 2014-10-29 22:27:34

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

而且我用这个在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; 

它现在:) 我希望这会帮助别人。

叶夫根

+0

您正在创建一个包含所有值的单个大字符串。这是否需要在数据库上投射?不知道这是否适用于所有数据库。 – AngerClown 2010-12-17 18:33:26

+0

感谢您的评论。你是对的。我只把它交给了DB Oracle。 – pevgen 2010-12-20 10:31:51

3

其他选项可以

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

恐怕叶夫根的解决方案似乎只是工作,因为有一个小错误的代码示例中:

inString = "," + element; 

这意味着inString始终只包含单个最后一个数字(而不是连接数字列表)。

这其实应该

inString += "," + element; 

唉,如果这个错误被纠正数据库启动报告“数目不正确”异常,因为MyBatis的集“1,2,3”作为一个字符串参数,并简单地数据库试图将此字符串转换为数字:/

另一方面,Mohit描述的@SelectProvider注释工作正常。 我们必须意识到,每次我们在IN子句中使用不同的参数运行查询时,它都会创建一个新语句,而不是重用现有的PreparedStatement(因为IN子句中的参数正在SQL中进行硬编码,而不是被设置为准备好的语句的参数)。这有时会导致数据库中的内存泄漏(因为数据库需要存储越来越多的准备好的语句,并且可能不会重用现有的执行计划)。

可以尝试混合@SelectProvider和自定义typeHandler。通过这种方式,可以根据需要使用@SelectProvider在“IN(...)”内创建一个包含尽可能多占位符的查询,然后在自定义TypeHandler中全部替换它们。虽然它有点棘手。

26

我相信答案与this question中给出的答案相同。您可以使用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注解。

+0

每当我这样做,我会得到一个异常:“org.apache.ibatis.binding.BindingException:Parameter'item'not found。”这个工作所需的最低版本的mybatis是什么? – Justin 2014-08-28 05:14:45

+0

这只适用于myBatis 3.我不确定确切的哪些次要版本支持它。还要确保在'

0

在Oracle中,我使用的Tom Kyte's tokenizer一个变体来处理未知列表的大小(因为甲骨文对IN子句和做多插件来绕过它的加重1K限制)。这是针对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; 
相关问题