Map.Entry 和 Map.entrySet() 集合的 MyBatis 错误
MyBatis Error on Collection of Map.Entry and Map.entrySet()
假设我们有 MyBatis 3.3.0 和 MyBatis-Spring 1.2.3 和一个简单的 select 查询...
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where
<foreach collection="properties" index="index" item="item" separator=" and ">
1 = #{id} AND 'a' = #{item.key,jdbcType=VARCHAR} AND 'b' = #{item.value,jdbcType=VARCHAR}
</foreach>
</select>
(应该简单 return 1,如果给定的 id 是 1 并且集合中给定的所有关键属性都是 "a" 并且所有值都是 "b")
...这使得一个简单的 TestMapper
接口方法...
Integer testSelect(Map<String, Object> arguments);
...我们用这个测试方法测试...
@Test
public void test_for_bug() {
final Map<String, Object> parameters = new HashMap<>();
parameters.put("id", 1);
final Map<String, String> entries = new HashMap<>();
entries.put("a", "b");
parameters.put("properties", entries.entrySet());
final Integer result = this.testMapper.testSelect(parameters);
assertThat(result).isEqualTo(1);
}
...我们将得到以下错误....
Type handler was null on parameter mapping for property
'__frch_item_0.value'. It was either not specified and/or could not
be found for the javaType / jdbcType combination specified.
原因似乎是对 item.value
的调用导致对字符串本身的 value
属性 的调用。不幸的是,我不知道为什么。
用 Collection
个自定义 Entry
对象(key
和 value
属性)替换 entries.entrySet()
效果很好。也很奇怪:这似乎只发生在 <collection>
中,直接给 Map.Entry
作为参数,比如...
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where 'b' = #{entry.value,jdbcType=VARCHAR}
</select>
...和...
@Test
public void test_for_bug() {
final Map<String, String> entries = new HashMap<>();
entries.put("a", "b");
final Map<String, Object> parameters = new HashMap<>();
parameters.put("entry", entries.entrySet().iterator().next());
final Integer result = this.testMapper.testSelect(parameters);
assertThat(result).isEqualTo(1);
}
...有效。
有人知道 Map.EntrySet 的问题出在哪里吗?有机会以某种方式修复它吗?当然,创建变通办法很容易,但恕我直言,不需要。
你只需传递参数映射而不是 map.entrySet(),像这样
parameters.put("properties", entries);
然后这样调用你的 mybatis
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where
<foreach collection="properties.entrySet()" index="index" item="item" separator=" and ">
1 = #{id} AND 'a' = #{item.key,jdbcType=VARCHAR} AND 'b' = #{item.value,jdbcType=VARCHAR}
</foreach>
</select>
似乎处理此问题(已提交文档更新)的正确方法如下(因为开发人员在几个版本前进行了一些更改):
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where
<foreach collection="properties" index="index" item="item" separator=" and ">
1 = #{id} AND 'a' = #{index,jdbcType=VARCHAR} AND 'b' = #{item,jdbcType=VARCHAR}
</foreach>
</select>
原因是,对于 Iterables
/Arrays
和 Maps
(和 Iteratable<Map.Entry>
对象),<foreach>
的行为略有不同:
- 对于
Iterable
或Array
,index
是当前迭代的编号,item
是本次迭代中从Iterable中检索到的元素。
- 对于
Map
(或Iterable<Map.Entry>
)index
是当前条目的键,item是当前条目的value
这解释了为什么 item.value
对于 Map<String, String>
实际上导致 String.value
(value
已经是 String
- 它有一个私有的 char array
成员调用了 value
,所以 MyBatis 试图访问 String.value
- 但失败了,因为 char array
不是映射类型)。
假设我们有 MyBatis 3.3.0 和 MyBatis-Spring 1.2.3 和一个简单的 select 查询...
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where
<foreach collection="properties" index="index" item="item" separator=" and ">
1 = #{id} AND 'a' = #{item.key,jdbcType=VARCHAR} AND 'b' = #{item.value,jdbcType=VARCHAR}
</foreach>
</select>
(应该简单 return 1,如果给定的 id 是 1 并且集合中给定的所有关键属性都是 "a" 并且所有值都是 "b")
...这使得一个简单的 TestMapper
接口方法...
Integer testSelect(Map<String, Object> arguments);
...我们用这个测试方法测试...
@Test
public void test_for_bug() {
final Map<String, Object> parameters = new HashMap<>();
parameters.put("id", 1);
final Map<String, String> entries = new HashMap<>();
entries.put("a", "b");
parameters.put("properties", entries.entrySet());
final Integer result = this.testMapper.testSelect(parameters);
assertThat(result).isEqualTo(1);
}
...我们将得到以下错误....
Type handler was null on parameter mapping for property '__frch_item_0.value'. It was either not specified and/or could not be found for the javaType / jdbcType combination specified.
原因似乎是对 item.value
的调用导致对字符串本身的 value
属性 的调用。不幸的是,我不知道为什么。
用 Collection
个自定义 Entry
对象(key
和 value
属性)替换 entries.entrySet()
效果很好。也很奇怪:这似乎只发生在 <collection>
中,直接给 Map.Entry
作为参数,比如...
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where 'b' = #{entry.value,jdbcType=VARCHAR}
</select>
...和...
@Test
public void test_for_bug() {
final Map<String, String> entries = new HashMap<>();
entries.put("a", "b");
final Map<String, Object> parameters = new HashMap<>();
parameters.put("entry", entries.entrySet().iterator().next());
final Integer result = this.testMapper.testSelect(parameters);
assertThat(result).isEqualTo(1);
}
...有效。
有人知道 Map.EntrySet 的问题出在哪里吗?有机会以某种方式修复它吗?当然,创建变通办法很容易,但恕我直言,不需要。
你只需传递参数映射而不是 map.entrySet(),像这样
parameters.put("properties", entries);
然后这样调用你的 mybatis
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where
<foreach collection="properties.entrySet()" index="index" item="item" separator=" and ">
1 = #{id} AND 'a' = #{item.key,jdbcType=VARCHAR} AND 'b' = #{item.value,jdbcType=VARCHAR}
</foreach>
</select>
似乎处理此问题(已提交文档更新)的正确方法如下(因为开发人员在几个版本前进行了一些更改):
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where
<foreach collection="properties" index="index" item="item" separator=" and ">
1 = #{id} AND 'a' = #{index,jdbcType=VARCHAR} AND 'b' = #{item,jdbcType=VARCHAR}
</foreach>
</select>
原因是,对于 Iterables
/Arrays
和 Maps
(和 Iteratable<Map.Entry>
对象),<foreach>
的行为略有不同:
- 对于
Iterable
或Array
,index
是当前迭代的编号,item
是本次迭代中从Iterable中检索到的元素。 - 对于
Map
(或Iterable<Map.Entry>
)index
是当前条目的键,item是当前条目的value
这解释了为什么 item.value
对于 Map<String, String>
实际上导致 String.value
(value
已经是 String
- 它有一个私有的 char array
成员调用了 value
,所以 MyBatis 试图访问 String.value
- 但失败了,因为 char array
不是映射类型)。