Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In MyBatis 3, dynamic SQL lets a mapper include, omit, or choose SQL fragments at runtime. Use <if> for optional conditions, <choose> for mutually exclusive branches, <where> and <set> to format conditional clauses, and <foreach> to build collection-based fragments. Keep values in #{} prepared parameters; ${} inserts raw text and must not receive untrusted input.

“MyBatis dynamic SQL” can also mean the separate MyBatis Dynamic SQL Java DSL. That library builds whole statements in Java rather than conditionally composing fragments in a mapper. The right choice depends on where your project keeps SQL and how its existing mappers are structured.

How MyBatis dynamic SQL works

MyBatis evaluates dynamic elements in a mapped statement and combines the resulting fragments into SQL. The main documented elements are <if>, <choose>, <trim>, <where>, <set>, <foreach>, and <bind>. You can use them in XML mapper files or inside a <script> element in an annotation-based mapper. See the MyBatis 3 dynamic SQL documentation.

Choose the right tag for the query

Use <if> for independent optional filters

An <if> element emits its contents when its OGNL test expression evaluates to true. This suits filters that can be combined independently, such as title and author criteria:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<select id="findPosts" resultType="Post">
  SELECT * FROM BLOG
  <where>
    <if test="title != null">
      AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
      AND author_name = #{author.name}
    </if>
  </where>
</select>

Each predicate is included only when its condition passes. Use the predicate order and conditions that reflect the search behavior your application intends.

Use <choose> when only one branch should apply

<choose> evaluates alternatives in order and emits a matching <when> branch; if none matches, <otherwise> supplies a fallback. It is useful when the query should prefer one search key rather than combine every supplied key:

<where>
  <choose>
    <when test="id != null">
      AND id = #{id}
    </when>
    <when test="email != null">
      AND email = #{email}
    </when>
    <otherwise>
      AND status = 'ACTIVE'
    </otherwise>
  </choose>
</where>

Decide explicitly what should happen when none of the preferred inputs is present; the fallback is query logic, not merely formatting.

Use <where> to manage optional predicates

<where> emits WHERE only when its body produces content and removes a leading AND or OR. This avoids a dangling clause when all optional conditions are absent and removes the conjunction from the first emitted predicate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For custom formatting, <trim prefix="WHERE" prefixOverrides="AND |OR "> offers equivalent control. Whitespace in prefixOverrides matters because MyBatis matches the specified text.

Use <set> for partial updates

<set> conditionally emits update assignments, adds the SET keyword, and removes a trailing comma. For custom formatting, use a trim element with prefix="SET" and suffixOverrides=",".

<update id="updatePost">
  UPDATE BLOG
  <set>
    <if test="title != null">title = #{title},</if>
    <if test="author != null">author = #{author},</if>
  </set>
  WHERE id = #{id}
</update>

Make sure the application’s inputs and validation rules prevent an update with no assignments from becoming an unintended operation; formatting tags do not define that business rule.

Use <foreach> for collections

<foreach> iterates over an iterable, map, or array and can add opening text, separators, and closing text. It is commonly used for an IN predicate:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<select id="findPostsByIds" resultType="Post">
  SELECT * FROM BLOG
  WHERE id IN
  <foreach item="id" collection="list" open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

Choose and test the application’s behavior for null and empty collections. Verify the rendered SQL and expected result for both; do not assume that an empty collection has the same meaning as an omitted filter.

Use <bind> for a derived parameter

<bind> creates a variable from an OGNL expression. For example, a mapper can form a LIKE pattern and still pass it as a bound value:

<bind name="pattern" value="'%' + title + '%'"/>
AND title like #{pattern}

The resulting value remains a parameter rather than raw SQL text.

Keep parameters safe

#{value} creates a prepared-statement parameter that MyBatis binds through JDBC. Use it for data values, including values inside dynamic fragments and <foreach>.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

${value} substitutes a string directly into SQL without escaping it. It can be useful when SQL must include dynamic metadata such as a column identifier, but accepting arbitrary user input there creates SQL injection risk. If a sort column or identifier must vary, map an application-controlled choice to an allow-listed identifier; do not pass raw user text to ${}. MyBatis explains the distinction in its SQL map XML documentation.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choose between mapper scripting and the Java DSL

The names are similar, but they refer to different ways of constructing SQL.

Approach Where SQL is authored What it does
MyBatis 3 dynamic SQL Mapper XML, or a <script> element in an annotation Conditionally assembles fragments within a mapped statement.
MyBatis Dynamic SQL library Java code A separate DSL builds complete DELETE, INSERT, SELECT, and UPDATE statements and parameter objects. Its WHERE support includes equality and other comparisons, IN, LIKE, BETWEEN, and null checks. See its introduction and WHERE clause documentation.
MyBatis SQL Builder Java code A core MyBatis facility for building SQL strings; it is distinct from the separately maintained MyBatis Dynamic SQL library. See the SQL Builder documentation.

There is no established universal winner or performance ranking between XML scripting and the separate Java DSL. Compare where your team wants to author queries, how much type guidance it wants from a DSL, how each option fits existing mapper interfaces and infrastructure, and whether the generated SQL and parameter handling behave correctly for your database. The DSL’s quick start describes its table and column representation, mapper setup, and use with MyBatis.

Handle database and project-specific behavior

SQL syntax can vary by database. When a databaseIdProvider is configured, MyBatis exposes _databaseId for choosing database-specific fragments. Treat each branch as dialect-specific SQL and validate it against the database the application actually uses.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The default MyBatis scripting language is XML. A custom language driver is an extension point if a project needs another scripting approach; ordinary dynamic queries do not require one.

Before adopting syntax or a library feature, check it against the MyBatis and library versions in the application’s dependencies. Generated SQL should be inspected or tested with the project’s actual database and expected inputs, especially for absent filters, empty collections, and partial updates.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.