Leveraging MyBatis Where and Trim Tags for Robust Dynamic Query Construction

When building dynamic conditional queries in MyBatis, manual WHERE clause construction can introduce syntax errors from leading AND/OR operators or a dagnling WHERE keyword when no filters apply. For example, this flawed query snippet demonstrates both issues:

<select id="fetchBlogEntry" resultType="BlogEntity">
  SELECT blog_id, title, content, publish_status FROM blog_catalog
  WHERE
  <if test="publishStatus != null">
    publish_status = #{publishStatus}
  </if>
  <if test="searchKeyword != null">
    AND title LIKE CONCAT('%', #{searchKeyword}, '%')
  </if>
</select>

If publishStatus is null, the generated SQL becomes SELECT ... WHERE AND title LIKE ...; if both conditions fail, it leaves SELECT ... WHERE. Both variants trigger database syntax exceptions.

Using MyBatis' <where> tag resolves this automatically. The tag inserts a WHERE clause only if at least one nested condition yields a valid SQL fragment. It also trims any leading AND or OR from the resulting filter block:

<select id="fetchBlogEntry" resultType="BlogEntity">
  SELECT blog_id, title, content, publish_status FROM blog_catalog
  <where>
    <if test="publishStatus != null">
      publish_status = #{publishStatus}
    </if>
    <if test="searchKeyword != null">
      AND title LIKE CONCAT('%', #{searchKeyword}, '%')
    </if>
  </where>
</select>

For more granular control over prefixes, suffixes, and trimming behavior, use the flexible <trim> tag, which can replicate <where> or <set> functionality and extend it. For a WHERE-like trim configuration:

<select id="loadUserProfiles" resultType="UserProfile">
  SELECT user_id, full_name, user_role FROM platform_users
  <trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="fullName != null and fullName.trim().length() > 0">
      AND full_name = #{fullName}
    </if>
    <if test="userRole != null and userRole.trim().length() > 0">
      AND user_role = #{userRole}
    </if>
  </trim>
</select>

Here, prefix="WHERE" adds the WHERE keyword only when nested conditions exist, and prefixOverrides="AND |OR " removes any leading AND/OR followed by a space from the filtered content.

The <trim> tag also works for UPDATE statements to handle trailing commas in SET clauses, plus add custom suffixes:

<update id="modifyUserProfile">
  UPDATE platform_users
  <trim prefix="SET" suffixOverrides="," suffix=" WHERE user_id = #{userId}">
    <if test="fullName != null and fullName.trim().length() > 0">
      full_name = #{fullName},
    </if>
    <if test="userRole != null and userRole.trim().length() > 0">
      user_role = #{userRole},
    </if>
  </trim>
</update>

In this UPDATE case, suffixOverrides="," strips the final comma from the SET assignmenst, while suffix=" WHERE user_id = #{userId}" appends the mandatory WHERE clause for the update operation.

Tags: MyBatis Dynamic SQL Java Persistence SQL Construction

Posted on Fri, 12 Jun 2026 16:51:00 +0000 by Mike521