Reputation: 10662
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.
Upvotes: 0
Views: 13496
Reputation: 10662
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):
<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>
The reason is, that <foreach>
behaves a little bit different for Iterables
/Arrays
and Maps
(and Iteratable<Map.Entry>
Objects):
Iterable
or Array
, index
is the number of the current iteration and item
is the element retrieved from the Iterable in this iteration.Map
(or Iterable<Map.Entry>
) index
is the current entry's key and item is the current entry's value
This explains why item.value
for a Map<String, String>
leads actually to String.value
(the value
is already a String
- which has a private char array
member called value
, so MyBatis is trying to access String.value
- and fails, because a char array
is not a mapped type).
Upvotes: 3
Reputation: 6738
You just pass parameter map instead of map.entrySet(), like this
parameters.put("properties", entries);
and call your mybatis like that
<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>
Upvotes: 0