Hei there, I'm working on a Primefaces app and as a persistence layer I chose Mybatis.
This is how a regular sql would look in my mapper:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<if test="( hasFilters == 'yes' ) and ( parameters != null )">
<where>
<foreach item="clause" collection="parameters" separator=" AND "
open="(" close=")">
UPPER(${clause.column}) ${clause.operator} #{clause.value}
</foreach>
</where>
</if>
<if test="sort == 'true'">
ORDER BY ${sortField}
<if test="sortOrder == 'DESC'"> DESC</if>
<if test="sortOder == 'ASC'"> ASC</if>
</if>
</select>
Almost all my queries use the dynamic sql part starting from the <if test...>
. Is it possible to put it in a separate file and then reuse it all over my queries?
There are several options how to reuse sql snippets.
SQL snippets and include
The first one is using include
. Create separate mapper Common.xml:
<mapper namespace="com.company.project.common">
<sql id="orderBy>
<if test="sort == 'true'">
ORDER BY ${sortField}
<if test="sortOrder == 'DESC'"> DESC</if>
<if test="sortOder == 'ASC'"> ASC</if>
</if>
</sql>
<sql id="filters">
<if test="( hasFilters == 'yes' ) and ( parameters != null )">
<where>
<foreach item="clause" collection="parameters" separator=" AND "
open="(" close=")">
UPPER(${clause.column}) ${clause.operator} #{clause.value}
</foreach>
</where>
</if>
</sql>
</mapper>
And the use it in other mappers MyMapper.xml
:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<include refid="com.company.project.common.filters"/>
<include refid="com.company.project.common.orderBy"/>
</select>
To avoid duplicating namespace in every include you can create shortcut snippets in MyMapper.xml
:
<sql id="orderBy">
<include refid="com.company.project.common.orderBy"/>
</sql>
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
<include refid="orderBy"/>
</select>
Mybatis-velocity macro
Another possible option is to use mybatis scripting. Using mybatis-velocity scripting engine you can define velocity macro and reusing it like this.
In Commons.xml
:
<sql id="macros"
#macro(filters)
#if ( $_parameter.hasFilters )
#repeat( $_parameter.parameters $clause "AND" " (" ")" )
${clause.column} ${clause.operator} @{clause.value}
#end
#end
#end
#macro(order_by)
..
#end
</sql>
In MyMapper.xml
:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
<include refid="macros"/>
SELECT * FROM SSLS_GUI.VW_TU
#filters()
#order_by()
</select>
Including macros via sql snippet is not the most clean way to reuse macros. It is just an idea how this is used.
Much better option is to configure mybatis-velocity and specify what global macros are available. In this case there will be no need to do include of macros
snippet and result query will be like this:
<select id="getAllTransportUnit" resultMap="TransportUnitMap">
SELECT * FROM SSLS_GUI.VW_TU
#filters()
#order_by()
</select>
See @Roman Konoval's answer for how to do this in XML.
For another option on the pure Java side (in OP's case, the XML option above is more applicable; I leave this here for those who may be using pure Java), one can use Mybatis' Statement Builders, which allow for the construction of dynamic SQL inline with Java code, you can factor out the common code there similar to the way you would factor out any common code.
The example they give in the Mybatis doc is as follows:
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
So, you could define a function that factors out your commmon dynamic SQL from your XML, and perhaps takes arguments representing the SELECT
columns and FROM
table portions of the statement (and anything else that might vary), which can then be passed in to the factored-out dynamic SQL methods inside of the function.