Mybatis框架的常用标签
本文最后更新于3 天前,其中的信息可能已经过时,如有错误请发送邮件到jingxushi13@gmail.com

MyBatis 标签全览

MyBatis 最核心的设计哲学是:SQL 归你写,动态拼接归框架做。mapper XML 里的那些标签,本质上就是一套 SQL 模板语言,把条件判断、循环、复用这些逻辑从 Java 代码里剥离出来,放到离 SQL 最近的地方。


一、顶层结构标签

这几个标签定义了一个 mapper 文件的骨架,缺一不可。

<mapper>

<mapper namespace="com.example.mapper.UserMapper">
  <!-- 所有 SQL 语句写在这里 -->
</mapper>

namespace 必须和对应的 Mapper 接口全限定名完全一致,MyBatis 靠这个做接口方法和 SQL 语句的绑定。

<select> / <insert> / <update> / <delete>

四个 CRUD 标签,分别对应四种 SQL 操作。常用属性:

属性说明
id对应接口方法名,同一 namespace 内唯一
resultType返回值类型,写全限定名或别名
resultMap指向 <resultMap>,处理复杂映射
parameterType参数类型,大多数情况 MyBatis 能自动推断,可省略
useGeneratedKeystrue 时自动将主键写回入参对象
keyProperty配合 useGeneratedKeys 指定接收主键的字段名
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (name, email) VALUES (#{name}, #{email})
</insert>

<select id="findById" resultType="com.example.entity.User">
    SELECT * FROM user WHERE id = #{id}
</select>

二、结果映射标签

当数据库列名和 Java 字段名不一致,或者涉及关联查询时,<resultMap> 是唯一正解。

<resultMap>

<resultMap id="userResultMap" type="User">
    <id     property="id"       column="user_id"/>
    <result property="userName" column="user_name"/>
    <result property="email"    column="email"/>
</resultMap>
  • <id> 标记主键列,对缓存和性能有影响,不要随便用 <result> 替代
  • <result> 映射普通列
  • type 是目标 Java 类型

<association> — 一对一关联

查询用户同时带出地址信息:

<resultMap id="userWithAddress" type="User">
    <id     property="id"   column="user_id"/>
    <result property="name" column="name"/>
    <association property="address" javaType="Address">
        <id     property="id"     column="addr_id"/>
        <result property="city"   column="city"/>
        <result property="street" column="street"/>
    </association>
</resultMap>

也可以用 select 属性做懒加载(N+1 要注意):

<association property="address"
             select="com.example.mapper.AddressMapper.findByUserId"
             column="id"
             fetchType="lazy"/>

<collection> — 一对多关联

一个订单包含多个订单项:

<resultMap id="orderResultMap" type="Order">
    <id property="id" column="order_id"/>
    <collection property="items" ofType="OrderItem">
        <id     property="id"       column="item_id"/>
        <result property="productName" column="product_name"/>
        <result property="quantity"    column="quantity"/>
    </collection>
</resultMap>

<discriminator> — 鉴别器

根据某个列的值决定用哪个子 resultMap,适合继承体系的实体:

<resultMap id="vehicleMap" type="Vehicle">
    <id property="id" column="id"/>
    <discriminator javaType="String" column="vehicle_type">
        <case value="CAR"   resultType="Car"/>
        <case value="TRUCK" resultType="Truck"/>
    </discriminator>
</resultMap>

三、动态 SQL 标签

这是 MyBatis 最精华的部分,解决了”根据参数动态拼 SQL”这个在 JDBC 时代极度痛苦的问题。

<if> — 条件判断

<select id="findUsers" resultType="User">
    SELECT * FROM user
    WHERE 1=1
    <if test="name != null and name != ''">
        AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="status != null">
        AND status = #{status}
    </if>
</select>

test 里写的是 OGNL 表达式,直接访问参数对象的属性,不用加 $#

<where> — 智能 WHERE 子句

上面用 WHERE 1=1 的写法比较老土,<where> 可以更优雅地处理:

<select id="findUsers" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null">AND name = #{name}</if>
        <if test="status != null">AND status = #{status}</if>
    </where>
</select>

<where> 会在有子句时自动加上 WHERE,并自动去掉开头多余的 ANDOR,不用操心前缀问题。

<set> — 智能 SET 子句

UPDATE 语句的好搭档,自动去掉末尾多余的逗号:

<update id="updateUser">
    UPDATE user
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="email != null">email = #{email},</if>
        <if test="status != null">status = #{status},</if>
    </set>
    WHERE id = #{id}
</update>

<choose> / <when> / <otherwise> — 多分支选择

相当于 Java 的 switch-case,只有第一个匹配的 <when> 会生效:

<select id="findByPriority" resultType="User">
    SELECT * FROM user
    <where>
        <choose>
            <when test="name != null">
                AND name = #{name}
            </when>
            <when test="email != null">
                AND email = #{email}
            </when>
            <otherwise>
                AND status = 'ACTIVE'
            </otherwise>
        </choose>
    </where>
</select>

<foreach> — 遍历集合

处理 IN 查询或批量插入的核心标签:

<!-- IN 查询 -->
<select id="findByIds" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsert">
    INSERT INTO user (name, email) VALUES
    <foreach collection="users" item="user" separator=",">
        (#{user.name}, #{user.email})
    </foreach>
</insert>
属性说明
collection集合参数名,List 传 list,数组传 array,Map 传 key 名
item每次迭代的元素变量名
index当前索引(List 是下标,Map 是 key)
open / close开头和结尾的字符
separator元素间的分隔符

注意:批量插入时单次插入量不要太大,建议每批 500~1000 条,否则会撑大 SQL 包,MySQL 默认 max_allowed_packet 是 4MB。

<trim> — 通用前后缀处理

<where><set> 本质上是 <trim> 的语法糖:

<!-- 等价于 <where> -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    ...
</trim>

<!-- 等价于 <set> -->
<trim prefix="SET" suffixOverrides=",">
    ...
</trim>

<trim> 给了你完全的控制权,适合非标准的场景。


四、SQL 复用标签

<sql><include>

把可复用的 SQL 片段提取出来,避免重复:

<sql id="userColumns">
    id, name, email, status, created_at
</sql>

<select id="findAll" resultType="User">
    SELECT <include refid="userColumns"/>
    FROM user
</select>

<select id="findById" resultType="User">
    SELECT <include refid="userColumns"/>
    FROM user WHERE id = #{id}
</select>

<include> 还支持传参(通过 <property>),配合 ${xxx} 插值实现更灵活的复用:

<sql id="tableName">${prefix}_user</sql>

<select id="findAll" resultType="User">
    SELECT * FROM <include refid="tableName">
        <property name="prefix" value="dev"/>
    </include>
</select>

五、参数引用:#{} vs ${}

这个问题在面试里出现的频率极高。

#{}${}
底层实现PreparedStatement 占位符 ?字符串直接拼接
SQL 注入风险(参数被转义)(原样拼入 SQL)
适用场景绝大多数参数值表名、列名、ORDER BY 字段等动态结构
<!-- 安全:用户输入的值一律用 #{} -->
SELECT * FROM user WHERE name = #{name}

<!-- 必须用 ${}:表名是运行时决定的 -->
SELECT * FROM ${tableName} WHERE id = #{id}

<!-- ORDER BY 字段名必须用 ${},但要在 Java 层做白名单校验! -->
SELECT * FROM user ORDER BY ${sortField} ${sortOrder}

如果在业务代码里收到前端传来的 sortField,一定要先用白名单校验,确认它是合法列名,再传给 MyBatis,否则有 SQL 注入风险。


六、缓存相关标签

<cache>

开启 mapper 级别的二级缓存:

<cache eviction="LRU"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

实际项目里,MyBatis 自带的二级缓存局限性较大(多表 join 时缓存失效粒度粗、集群环境下缓存不共享),通常建议在 Service 层自己用 Redis 做缓存,关掉 MyBatis 二级缓存以免产生脏数据。

<cache-ref>

让当前 mapper 共享另一个 mapper 的缓存:

<cache-ref namespace="com.example.mapper.UserMapper"/>

七、一个完整例子串联所有标签

<mapper namespace="com.example.mapper.OrderMapper">

    <!-- 复用的列片段 -->
    <sql id="orderColumns">
        o.id, o.user_id, o.total_amount, o.status, o.created_at
    </sql>

    <!-- 复杂结果映射 -->
    <resultMap id="orderDetail" type="Order">
        <id     property="id"          column="id"/>
        <result property="totalAmount" column="total_amount"/>
        <result property="status"      column="status"/>
        <association property="user" javaType="User">
            <id     property="id"   column="user_id"/>
            <result property="name" column="user_name"/>
        </association>
        <collection property="items" ofType="OrderItem">
            <id     property="id"          column="item_id"/>
            <result property="productName" column="product_name"/>
            <result property="quantity"    column="quantity"/>
        </collection>
    </resultMap>

    <!-- 动态查询 -->
    <select id="queryOrders" resultMap="orderDetail">
        SELECT
            <include refid="orderColumns"/>,
            u.name AS user_name,
            i.id AS item_id, i.product_name, i.quantity
        FROM orders o
        LEFT JOIN user u ON o.user_id = u.id
        LEFT JOIN order_item i ON o.id = i.order_id
        <where>
            <if test="userId != null">AND o.user_id = #{userId}</if>
            <if test="status != null">AND o.status = #{status}</if>
            <if test="startTime != null">AND o.created_at &gt;= #{startTime}</if>
            <if test="endTime != null">AND o.created_at &lt;= #{endTime}</if>
        </where>
        ORDER BY o.created_at DESC
    </select>

    <!-- 批量更新状态 -->
    <update id="batchUpdateStatus">
        UPDATE orders SET status = #{status}
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </update>

</mapper>

注意 XML 里的 >< 要用转义字符 &gt;&lt;,或者把整段 SQL 包在 <![CDATA[ ... ]]> 里。


小结

MyBatis 的标签体系设计得很克制,没有过度封装,核心逻辑就这几层:

  • CRUD 四件套 定义语句
  • resultMap 家族 处理映射
  • 动态 SQL 六标签(if / where / set / choose / foreach / trim)处理条件拼接
  • sql + include 处理复用
  • #{} 做参数,${} 做结构

No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
Previous