Backend Development 20 min read

Master MyBatis Dynamic SQL: 9 Essential Tag Techniques for Cleaner Code

This article walks through MyBatis dynamic SQL tags—foreach, concat, choose, selectKey, if, where, trim, set, and sql fragments—explaining their attributes, usage patterns, and common pitfalls, and provides complete XML and Java code examples for each technique.

macrozheng
macrozheng
macrozheng
Master MyBatis Dynamic SQL: 9 Essential Tag Techniques for Cleaner Code

MyBatis greatly simplifies Java data access, but configuring XML can still be cumbersome; using proper dynamic‑SQL tags saves time and reduces errors. Below are nine high‑quality patterns with explanations and full code samples.

1. foreach tag for iterating collections

The

foreach

element supports attributes

item

,

index

,

collection

,

open

,

separator

, and

close

. The

collection

attribute must be set correctly:

item

: alias for each element.

index

: loop index.

open

: prefix string, e.g., "(".

separator

: delimiter between items.

close

: suffix string, e.g., ")".

Three cases for

collection

:

If a single

List

parameter is passed, use

list

.

If a single array is passed, use

array

.

If multiple parameters are passed, MyBatis wraps them into a

Map

; the key is the parameter name, so

collection

should be the map key that holds the list or array.

MyBatis automatically wraps a List or array into a Map with keys list and array respectively.
<code>public List&lt;Entity&gt; queryById(List&lt;String&gt; userids);</code>
<code>&lt;select id="queryById" resultMap="BaseResultMap"&gt;
  SELECT * FROM entity
  WHERE id IN
  &lt;foreach collection="userids" item="userid" open="(" separator="," close=")"&gt;
    #{userid}
  &lt;/foreach&gt;
&lt;/select&gt;</code>

2. concat for fuzzy queries

<code>&lt;select id="queryById" resultMap="BaseResultMap" parameterType="entity"&gt;
  SELECT * FROM entity
  &lt;where&gt;
    &lt;if test="name != null"&gt;
      name LIKE concat('%', concat(#{name}, '%'))
    &lt;/if&gt;
  &lt;/where&gt;
&lt;/select&gt;</code>

3. choose (when, otherwise) tag

<code>&lt;select id="getUserList_choose" resultMap="resultMap_user" parameterType="com.yiibai.pojo.User"&gt;
  SELECT * FROM User u
  &lt;where&gt;
    &lt;choose&gt;
      &lt;when test="username != null"&gt;
        u.username LIKE CONCAT(CONCAT('%', #{username}), '%')
      &lt;/when&gt;
      &lt;when test="sex != null and sex != ''"&gt;
        AND u.sex = #{sex}
      &lt;/when&gt;
      &lt;when test="birthday != null"&gt;
        AND u.birthday = #{birthday}
      &lt;/when&gt;
      &lt;otherwise/&gt;
    &lt;/choose&gt;
  &lt;/where&gt;
&lt;/select&gt;</code>

4. selectKey for auto‑generated keys

<code>&lt;insert id="createStudentAutoKey" parameterType="StudentEntity" keyProperty="studentId"&gt;
  &lt;selectKey keyProperty="studentId" resultType="String" order="BEFORE"&gt;
    SELECT nextval('student')
  &lt;/selectKey&gt;
  INSERT INTO STUDENT_TBL (STUDENT_ID, STUDENT_NAME, STUDENT_SEX, STUDENT_BIRTHDAY, STUDENT_PHOTO, CLASS_ID, PLACE_ID)
  VALUES (#{studentId}, #{studentName}, #{studentSex}, #{studentBirthday}, #{studentPhoto, javaType=byte[], jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler}, #{classId}, #{placeId})
&lt;/insert&gt;</code>
<code>StudentEntity entity = new StudentEntity();
entity.setStudentName("黎明你好");
entity.setStudentSex(1);
entity.setStudentBirthday(DateUtil.parse("1985-05-28"));
entity.setClassId("20000001");
entity.setPlaceId("70000001");
this.dynamicSqlMapper.createStudentAutoKey(entity);
System.out.println("新增学生ID: " + entity.getStudentId());</code>

5. if tag for conditional clauses

<code>&lt;select id="getStudentList_if" resultMap="studentResultMap" parameterType="StudentEntity"&gt;
  SELECT * FROM STUDENT_TBL ST
  &lt;where&gt;
    &lt;if test="studentName != null"&gt;
      ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}), '%')
    &lt;/if&gt;
    &lt;if test="studentSex != null and studentSex != ''"&gt;
      AND ST.STUDENT_SEX = #{studentSex}
    &lt;/if&gt;
    &lt;if test="studentBirthday != null"&gt;
      AND ST.STUDENT_BIRTHDAY = #{studentBirthday}
    &lt;/if&gt;
    &lt;if test="classId != null and classId != ''"&gt;
      AND ST.CLASS_ID = #{classId}
    &lt;/if&gt;
    &lt;if test="placeId != null and placeId != ''"&gt;
      AND ST.PLACE_ID = #{placeId}
    &lt;/if&gt;
    &lt;if test="studentId != null and studentId != ''"&gt;
      AND ST.STUDENT_ID = #{studentId}
    &lt;/if&gt;
  &lt;/where&gt;
&lt;/select&gt;</code>
<code>public void select_test_2_1() {
    StudentEntity entity = new StudentEntity();
    entity.setStudentName("");
    entity.setStudentSex(1);
    entity.setStudentBirthday(DateUtil.parse("1985-05-28"));
    entity.setClassId("20000001");
    List<StudentEntity> list = this.dynamicSqlMapper.getStudentList_if(entity);
    for (StudentEntity e : list) {
        System.out.println(e);
    }
}</code>

6. if + where combination

<code>&lt;select id="getStudentList_whereIf" resultMap="studentResultMap" parameterType="StudentEntity"&gt;
  SELECT ST.STUDENT_ID, ST.STUDENT_NAME, ST.STUDENT_SEX, ST.STUDENT_BIRTHDAY, ST.STUDENT_PHOTO, ST.CLASS_ID, ST.PLACE_ID
  FROM STUDENT_TBL ST
  &lt;where&gt;
    &lt;if test="studentName != null"&gt;
      ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}), '%')
    &lt;/if&gt;
    &lt;if test="studentSex != null and studentSex != ''"&gt;
      AND ST.STUDENT_SEX = #{studentSex}
    &lt;/if&gt;
    &lt;if test="studentBirthday != null"&gt;
      AND ST.STUDENT_BIRTHDAY = #{studentBirthday}
    &lt;/if&gt;
    &lt;if test="classId != null and classId != ''"&gt;
      AND ST.CLASS_ID = #{classId}
    &lt;/if&gt;
    &lt;if test="placeId != null and placeId != ''"&gt;
      AND ST.PLACE_ID = #{placeId}
    &lt;/if&gt;
    &lt;if test="studentId != null and studentId != ''"&gt;
      AND ST.STUDENT_ID = #{studentId}
    &lt;/if&gt;
  &lt;/where&gt;
&lt;/select&gt;</code>

7. if + set for update statements

<code>&lt;update id="updateStudent_if_set" parameterType="StudentEntity"&gt;
  UPDATE STUDENT_TBL
  &lt;set&gt;
    &lt;if test="studentName != null and studentName != ''"&gt;
      STUDENT_TBL.STUDENT_NAME = #{studentName},
    &lt;/if&gt;
    &lt;if test="studentSex != null and studentSex != ''"&gt;
      STUDENT_TBL.STUDENT_SEX = #{studentSex},
    &lt;/if&gt;
    &lt;if test="studentBirthday != null"&gt;
      STUDENT_TBL.STUDENT_BIRTHDAY = #{studentBirthday},
    &lt;/if&gt;
    &lt;if test="studentPhoto != null"&gt;
      STUDENT_TBL.STUDENT_PHOTO = #{studentPhoto, javaType=byte[], jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler},
    &lt;/if&gt;
    &lt;if test="classId != ''"&gt;
      STUDENT_TBL.CLASS_ID = #{classId},
    &lt;/if&gt;
    &lt;if test="placeId != ''"&gt;
      STUDENT_TBL.PLACE_ID = #{placeId},
    &lt;/if&gt;
  &lt;/set&gt;
  WHERE STUDENT_TBL.STUDENT_ID = #{studentId};
&lt;/update&gt;</code>

8. trim to replace where / set

<code>&lt;select id="getStudentList_if_trim" resultMap="studentResultMap"&gt;
  SELECT ST.* FROM STUDENT_TBL ST
  &lt;trim prefix="WHERE" prefixOverrides="AND|OR"&gt;
    &lt;if test="studentName != null"&gt;
      ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}), '%')
    &lt;/if&gt;
    &lt;if test="studentSex != null and studentSex != ''"&gt;
      AND ST.STUDENT_SEX = #{studentSex}
    &lt;/if&gt;
    ...
  &lt;/trim&gt;
&lt;/select&gt;</code>
<code>&lt;update id="updateStudent_if_trim" parameterType="StudentEntity"&gt;
  UPDATE STUDENT_TBL
  &lt;trim prefix="SET" suffixOverrides=","&gt;
    &lt;if test="studentName != null and studentName != ''"&gt;
      STUDENT_TBL.STUDENT_NAME = #{studentName},
    &lt;/if&gt;
    &lt;if test="studentSex != null and studentSex != ''"&gt;
      STUDENT_TBL.STUDENT_SEX = #{studentSex},
    &lt;/if&gt;
    ...
  &lt;/trim&gt;
  WHERE STUDENT_TBL.STUDENT_ID = #{studentId}
&lt;/update&gt;</code>

9. foreach for IN clauses (array and list)

<code>&lt;select id="getStudentListByClassIds_foreach_array" resultMap="studentResultMap"&gt;
  SELECT ... FROM STUDENT_TBL ST
  WHERE ST.CLASS_ID IN
  &lt;foreach collection="array" item="classIds" open="(" separator="," close=")"&gt;
    #{classIds}
  &lt;/foreach&gt;
&lt;/select&gt;</code>
<code>@Test
public void test7_foreach() {
    String[] classIds = {"20000001", "20000002"};
    List<StudentEntity> list = this.dynamicSqlMapper.getStudentListByClassIds_foreach_array(classIds);
    for (StudentEntity e : list) {
        System.out.println(e);
    }
}</code>
<code>&lt;select id="getStudentListByClassIds_foreach_list" resultMap="studentResultMap"&gt;
  SELECT ... FROM STUDENT_TBL ST
  WHERE ST.CLASS_ID IN
  &lt;foreach collection="list" item="classIdList" open="(" separator="," close=")"&gt;
    #{classIdList}
  &lt;/foreach&gt;
&lt;/select&gt;</code>
<code>@Test
public void test7_2_foreach() {
    ArrayList<String> classIdList = new ArrayList<>();
    classIdList.add("20000001");
    classIdList.add("20000002");
    List<StudentEntity> list = this.dynamicSqlMapper.getStudentListByClassIds_foreach_list(classIdList);
    for (StudentEntity e : list) {
        System.out.println(e);
    }
}</code>

SQL fragment reuse

<code>&lt;sql id="orderAndItem"&gt;
  o.order_id, o.cid, o.address, o.create_date, o.orderitem_id, i.orderitem_id, i.product_id, i.count
&lt;/sql&gt;

&lt;select id="findOrderAndItemsByOid" parameterType="java.lang.String" resultMap="BaseResultMap"&gt;
  SELECT
  &lt;include refid="orderAndItem"/&gt;
  FROM ordertable o
  JOIN orderitem i ON o.orderitem_id = i.orderitem_id
  WHERE o.order_id = #{orderId}
&lt;/select&gt;</code>

These patterns help you write concise, maintainable MyBatis mappings and avoid common pitfalls such as extra commas, misplaced AND/OR, and incorrect collection handling.

JavadatabaseMyBatisXMLDynamic SQL
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.