assuming we have MyBatis 3.3.0 and MyBatis-Spring 1.2.3 and a simple select query…
<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>
(should simple return 1, if the id given is 1 and all key properties given in the collection are "a" and all values "b")
…which makes a simple TestMapper
interface method…
Integer testSelect(Map<String, Object> arguments);
…and we test it with this test method…
@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);
}
…we will get the following error….
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.
The reason for that seems to be that the call to item.value
results in a call of the value
property of the String itself. Unfortunately, I have no clue, why.
Replacing the entries.entrySet()
with a Collection
of custom Entry
objects (with key
and value
property) works fine. Also strange: This only seems to happen inside a <collection>
, giving a Map.Entry
directly as a parameter, like…
<select id="testSelect" parameterType="map" resultType="Integer">
select 1 from dual where 'b' = #{entry.value,jdbcType=VARCHAR}
</select>
…and…
@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);
}
…works.
Has anyone an idea what the problem with Map.EntrySet is here? Any chance to get it fixed somehow? Of course creating a workaround is easy enough, but imho it should not be needed.
Best Answer
Seems that the correct way to handle this (documentation update already submitted) is the following (since the developers made some changes a few versions ago):
The reason is, that
<foreach>
behaves a little bit different forIterables
/Arrays
andMaps
(andIteratable<Map.Entry>
Objects):Iterable
orArray
,index
is the number of the current iteration anditem
is the element retrieved from the Iterable in this iteration.Map
(orIterable<Map.Entry>
)index
is the current entry's key and item is the current entry'svalue
This explains why
item.value
for aMap<String, String>
leads actually toString.value
(thevalue
is already aString
- which has a privatechar array
member calledvalue
, so MyBatis is trying to accessString.value
- and fails, because achar array
is not a mapped type).