MyBatis iterate hashmap with foreach - mybatis

I have a small problem with MyBatis. I'm trying to iterate over a Hashmap but it seems, that MyBatis is not able to do it. The name of the Hshmap is serviceMap. Here is the code of the MyBatis foreach:
<foreach item="item" index="key" collection="serviceMap" open="(" separator="or" close=")">
(upper(p.ENDPOINT) like upper(#{key})
and
upper(p.ENDPOINT_OPERATION) like upper(#item))
</foreach>
The exception is:
org.apache.ibatis.builder.BuilderException: Error evaluating expression 'serviceMap'. Return value ({vehicle=register}) was not iterable.
at org.apache.ibatis.builder.xml.dynamic.ExpressionEvaluator.evaluateIterable(ExpressionEvaluator.java:59)
at org.apache.ibatis.builder.xml.dynamic.ForEachSqlNode.apply(ForEachSqlNode.java:51)
at org.apache.ibatis.builder.xml.dynamic.MixedSqlNode.apply(MixedSqlNode.java:29)
at org.apache.ibatis.builder.xml.dynamic.IfSqlNode.apply(IfSqlNode.java:31)
at org.apache.ibatis.builder.xml.dynamic.MixedSqlNode.apply(MixedSqlNode.java:29)
Is MyBatis simply not able to do it or did I make an mistake?
It would be really nice, if anybody can help me.
Thanks a lot!
Stefan

A HashMap is not iterable. You need to iterate the entrySet.
<foreach item="item" index="key" collection="serviceMap.entrySet" open="(" separator="or" close=")">
(upper(p.ENDPOINT) like upper(#{key})
and
upper(p.ENDPOINT_OPERATION) like upper(#item))

<foreach item="item" index="key" collection="serviceMap.entrySet()" open="((" separator="),(" close="))">
(upper(p.ENDPOINT) like upper(#{item.key})
and
upper(p.ENDPOINT_OPERATION) like upper(#item.value))
</foreach>
Use the exact hashmap mechanism

Related

How can I use <bind> within a loop while using MyBatis?

I wanted to build a sql like "(column like 'a%' or column like 'b%' or ...)" and a,b,etc. are elements in a collection.
So I came up with xml file like this
<foreach item="item" collection="items" open="(" separator=" or " close=")">
<bind name="pattern" value="item + '%'" />
column like #{pattern}
</foreach
But it didn't work out as I thought it would be.
The sql was like "(column like 'c%' or column like 'c%' or ...)" which c is the last element in the collection.
Now I use a CONCAT function to do the job.
column like CONCAT(#{pattern},'%')
Is there other good ways to do the job?
Thanks.
It is a known limitation i.e. <bind /> cannot be used inside <foreach /> and there is an open issue.
Using CONCAT is a good workaround for now.

How to avoid getters when using mybatis dynamic sql?

In mybatis we can write an update statement like this:
<update id="update" parameterType="com.test.User">
update USERS
<set>
<if test="user.name != null">NAME=#{user.name},</if>
<if test="user.age != null">AGE=#{user.age}</if>
</set>
</update>
Then it requires User class to have getters for name and age properties, without the update statement, the mapping can work without adding any getters, is there a way to avoid the getters for the update statement?
It should work if you use the latest version 3.5.1 (the change was made in 3.5.0).
If you cannot upgrade for some reason, it may still work if you 1) remove the #Param("user") from the method parameter and 2) reference the fields directly as follows.
<update id="update">
update USERS
<set>
<if test="name != null">NAME=#{name},</if>
<if test="age != null">AGE=#{age}</if>
</set>
</update>
The direct property reference works only when the bean (i.e. User) is the only parameter to the mapper method.

How can I reuse an SQL fragment with parameters?

I'm intending to make a fragment for reusing with parameters.
<insert ...>
<selectKey keyProperty="id" resultType="_long" order="BEFORE">
<choose>
<when test="_databaseId == 'derby'">
VALUES NEXT VALUE FOR SOME_ID_SEQ
</when>
<otherwise>
SELECT SOME_ID_SEQ.NEXTVAL FROM DUAL
</otherwise>
</choose>
</selectKey>
INSERT INTO ...
</insert>
Can I make an SQL fragment using parameters?
<sql id="selectKeyFromSequence">
<selectKey keyProperty="id" resultType="_long" order="BEFORE">
<choose>
<when test="_databaseId == 'derby'">
VALUES NEXT VALUE FOR #{sequenceName}
</when>
<otherwise>
SELECT #{sequenceName}.NEXTVAL FROM DUAL
</otherwise>
</choose>
</selectKey>
</sql>
So that I can reuse them like this?
<insert ...>
<include refid="...selectKeyFromSequence"/> <!-- How can I pass a parameter? -->
INSERT INTO ...
</insert>
Is this possible?
As of version 3.3.0 you can do it like this:
<sql id="myinclude">
from ${myproperty}
</sql>
<include refid="myinclude">
<property name="myproperty" value="tablename"/>
</include>
See section SQL in http://www.mybatis.org/mybatis-3/sqlmap-xml.html
You cannot pass parameter to tags. There is a similar SO question, iBatis issue and a MyBatis issue.
Includes are in-lined when the xmls are parsed so the do not exist as
their own once the startup process finishes (from MyBatis issue).
However, you can use variables inside tags. You do not pass it as a parameter but you can give it as a parameter to the function that has the include tag. You need to use the same variable name in all functions, i.e. #{sequenceName}.

Nant nested if condition

I am trying to do certain things when certain conditions are met, but it seems that somewhere my syntax is wrong. I need to have three if conditions to do one thing, but I'm not sure how to put in one of the if statements.
<if test="${build.MoniXXXtor == 'true'} or test="${build.XXX== 'true'}" or test="${build.yyy== 'true'}">
<property name="solutionFile" value="${svn.Lib.path}"/>
<property name="LocalPath" value="${Local.Lib.path}"/>
<call target="getLatest" if="${source.getLatest == 'true'}"/>
</if>
It seems the syntax for the OR condition above is wrong.
According to the documentation, you should use only one equals sign ( = instead of ==), e.g:
<if test="${build.configuration='release'}">
<echo>Build release configuration</echo>
</if>
Assuming that build.MoniXXXtor, build.XXX, and build.yyy are boolean properties the code is simply:
<if test="${build.MoniXXXtor or build.XXX or build.yyy}">
If they are string properties, they need to be converted to booleans:
<if test="${convert::to-boolean(build.MoniXXXtor) or
convert::to-boolean(build.XXX) or
convert::to-boolean(build.yyy)}">

MyBatis multiple resultsets

I am currently migrating code from iBatis 2 to MyBatis 3. I have a function that returns multiple results sets which we map to different classes. In iBatis we where able to map the different results using a comma separated list int the resultType like so:
<select id="findCashItems" parameterType="map" resultType="AdminCashBalance, AdminCashMovement, AdminCashTrx">
exec RequestActualAdministrativeData #{portfolioId}
</select>
But this does not appear to work in MyBatis 3. I can't find anything in the documentation except for a configuration item that, by default, enables multiple resultsets. But nothing on how to actually process them.
Using a resultMap with a comma separated list of result maps fixes this issue.
<resultMap id="adminCashBalance" type="AdminCashBalance">
...
</resultMap>
<resultMap id="adminCashMovement" type="AdminCashMovement">
...
</resultMap>
<resultMap id="adminCashTrx" type="AdminCashTrx">
...
</resultMap>
<select id="findCashItems" parameterType="map" resultMap="adminCashBalance, adminCashMovement, adminCashTrx">
exec RequestActualAdministrativeData #{portfolioId}
</select>