我们只想使用MyBatis的注释;我们真的想避免使用xml。我们试图使用“IN”条款:
@Select("SELECT * FROM blog WHERE id IN (#{ids})")
List<Blog> selectBlogs(int[] ids);
MyBatis似乎无法选择整数数组并将其放入生成的查询中。它似乎“软弱地失败”,我们没有得到任何结果。
看起来我们可以使用XML映射来实现这一点,但我们真的想避免这种情况。是否有正确的注释语法?
答案 0 :(得分:35)
我相信答案与this question中给出的答案相同。您可以通过执行以下操作在注释中使用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注释。
答案 1 :(得分:22)
我认为这是jdbc准备好的陈述的细微差别,而不是MyBatis。有一个链接here可以解释这个问题并提供各种解决方案。不幸的是,这些解决方案都不适用于您的应用程序,但是,它仍然是一个很好的阅读,以理解关于“IN”子句的预准备语句的局限性。可以在特定于DB的方面找到解决方案(可能是次优的)。例如,在postgresql中,可以使用:
"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"
“ANY”与“IN”相同,“:: int []”是将参数类型转换为int数组。进入语句的参数应该类似于:
"{1,2,3,4}"
答案 2 :(得分:15)
对这个话题做了一些研究。
@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
。但是,您仍然需要在任何地方写\"
。我的项目采用解决方案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);
答案 3 :(得分: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;
现在有效:) 我希望这会对某人有所帮助。
叶夫
答案 4 :(得分: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);
}
}
答案 5 :(得分: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));
}
}
答案 6 :(得分:0)
在Oracle中,我使用Tom Kyte's tokenizer的变体来处理未知的列表大小(给定Oracle对IN子句的1k限制以及多个IN的恶化以绕过它)。这适用于varchar2,但它可以针对数字进行定制(或者您可以依靠Oracle知道&#39; 1&#39; = 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;
答案 7 :(得分: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
的效果,所以如果重要的话,请测试一下
答案 8 :(得分:0)
我是用 postgresql
完成的。
@Update('''
UPDATE sample_table
SET start = null, finish = null
WHERE id=ANY(#{id});
''')
int resetData(@Param("id") String[] id)
ANY
的工作方式类似于 IN
。
上面的代码使用了 groovy
,但可以通过将单引号替换为双引号将其转换为 java
。