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.
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).
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>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.