動態 SQL 是 MyBatis 最核心、最實用的特性。本文系統講解 if、choose/when/otherwise、trim/where/set、foreach、bind 五大核心標簽,結合實戰 XML Mapper 示例,展示條件查詢、動態更新、批量插入、IN 條件構建等高頻場景,並給出演示項目完整代碼結構。
| 標簽 | 用途 | 關鍵屬性 |
|---|---|---|
<if> | 條件包含 SQL 片段 | test="OGNL 表達式" |
<choose> <when> <otherwise> | 互斥分支選擇 (類似 switch) | test 依次判斷,otherwise 兜底 |
<where> | 智能 WHERE 子句:自動去除首個 AND/OR、補全 WHERE 關鍵字 | 無屬性 |
<trim> | 通用字符串修剪:prefix/suffix/prefixOverrides/suffixOverrides | 極其靈活,可模擬 where/set |
<set> | 智能 SET 子句:自動去除尾部逗號、補全 SET 關鍵字 | 無屬性 |
<foreach> | 集合遍歷構建 IN 列表、批量 VALUES、UNION ALL 等 | collection/item/index/open/separator/close |
<bind> | OGNL 表達式結果綁定為變量,供後續引用 | name/value |
<where> + <if><select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null"> state = #{state} </if>
<if test="title != null"> AND title LIKE #{title} </if>
<if test="author != null and author.name != null"> AND author_name LIKE #{author.name} </if>
</where>
</select>
效果:若所有條件為 null → SELECT * FROM BLOG;若只有 title → SELECT * FROM BLOG WHERE title LIKE ? (自動去除 AND)。
<choose><select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null"> AND title LIKE #{title} </when>
<when test="author != null and author.name != null"> AND author_name LIKE #{author.name} </when>
<otherwise> AND featured = 1 </otherwise>
</choose>
</select>
<set> + <if><update id="updateAuthorIfNecessary">
UPDATE AUTHOR
<set>
<if test="username != null"> username = #{username}, </if>
<if test="password != null"> password = #{password}, </if>
<if test="email != null"> email = #{email}, </if>
<if test="bio != null"> bio = #{bio} </if>
</set>
WHERE id = #{id}
</update>
效果:只更新非 null 字段,自動處理逗號,避免 SET username=?, password=?, 尾逗號語法錯誤。
<foreach>-- 批量插入 (Oracle dual + UNION ALL)
<insert id="svaeTalentTypeBatch" parameterType="list">
INSERT INTO APP_ZONE_USER_TALENT_TYPE (ID, TALENT_ID, TYPE_CODE)
SELECT SEQ_TALENT_TYPE_ID.NEXTVAL, t.* FROM (
<foreach collection="list" item="item" index="index" separator="UNION ALL">
SELECT #{item.talentId}, #{item.typeCode} FROM DUAL
</foreach>
) t
</insert>
-- IN 條件查詢
<select id="selectPostIn" resultType="Post">
SELECT * FROM POST WHERE ID IN
<foreach collection="list" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
</select>
-- 批量刪除
<delete id="delDetailType">
DELETE FROM APP_ZONE_USER_TALENT_TYPE WHERE ID IN
<foreach collection="ids" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</delete>
<bind> 簡化複雜表達式<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG WHERE title LIKE #{pattern}
</select>
databaseIdProvider 後,動態 SQL 可用 _databaseId 區分方言 (Oracle/DB2/MySQL),構建特定序列/分頁/鎖語句。LanguageDriver 接口,配置 defaultScriptingLanguage 或方法級 @Lang,支持 Velocity、Freemarker、Groovy 等模板引擎編寫動態 SQL。Mapper XML (含 ResultMap、批量操作、動態更新、條件查詢):
<mapper namespace="cn.ffcs.zone.detailType.persistent.DetailTypeMapper">
<resultMap id="BaseResultMap" type="DetailType"> ... </resultMap>
<select id="findDetailTypes" resultMap="BaseResultMap"> ... </select>
<insert id="saveEntity"> <selectKey .../> INSERT ... </insert>
<insert id="svaeTalentTypeBatch" parameterType="list"> ... <foreach .../> ... </insert>
<delete id="delDetailType"> ... <foreach .../> ... </delete>
</mapper>
Mapper 接口:
public interface UserCardMapper extends BaseMapper<UserCard> {
int saveUserCard(UserCard uc);
@Select("SELECT * FROM app_zone_user_card WHERE user_id=#{userId}")
@ResultMap("BaseResultMap") UserCard findByUserId(@Param("userId") Long userId);
// ... 其他查詢/更新方法
List<UserCard> findByParam(Map<String, Object> param); // 通用條件查詢
}
BaseMapper:定義通用 CRUD + 分頁查詢契約。
<where>/<set>:自動處理關鍵字與 AND/OR/逗號,減少手動拼接錯誤。<foreach> 批量操作:單條 SQL 完成批量 INSERT/DELETE/IN,性能遠超循環調用。test="@Ognl@isNotNull(x)" 避免空指針;複雜邏輯用 <bind> 提取變量提升可讀性。#{prop,jdbcType=VARCHAR} 指定 JDBC 類型,避免類型推斷失敗 (尤其 Oracle/PostgreSQL)。<sql id="cols">...</sql> + <include refid="cols"/> 減少重複。