跳转至

使用 MyBatis 遇到过哪些坑?

一、#{}${} 的区别

  • #{}:预编译,? 占位符,防 SQL 注入。
  • ${}:字符串直接拼接,有 SQL 注入风险。
<!-- 安全 -->
WHERE name = #{name}

<!-- 排序字段必须用 $,因为列名不能用 ? 占位 -->
ORDER BY ${column}

二、N+1 查询问题

关联查询时,主表查一次,每条主记录又发一次子查询。

<select id="getOrder" resultMap="orderMap">
    SELECT * FROM order WHERE id = #{id}
</select>

<resultMap id="orderMap" type="Order">
    <collection property="items"
                select="getItemsByOrderId"
                column="id"/>
</resultMap>

查 100 个订单就发 101 次 SQL。

解决:用 JOIN + <collection>fetchType="join",或用 <association> 一次性查出来。

三、一级缓存失效

MyBatis 一级缓存默认开启,基于 SqlSession。但以下情况会失效:

  1. 不同 SqlSession。
  2. 同一个 SqlSession 但中间执行了 insert/update/delete
  3. 手动调用 clearCache()
  4. 配置了 flushCache="true"

四、二级缓存坑多

二级缓存跨 SqlSession,但:

  • 只能缓存序列化对象,且会序列化。
  • 多表关联时容易脏数据。
  • 分布式环境下各节点缓存不一致。

生产环境建议关闭二级缓存,用 Redis。

五、resultTyperesultMap

  • resultType:按字段名自动映射,适合简单查询。
  • resultMap:自定义映射,处理下划线转驼峰、关联查询。

下划线转驼峰全局配置:

mybatis.configuration.map-underscore-to-camel-case=true

六、foreach 拼接 in

<select id="selectByIds">
    SELECT * FROM user WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

坑: - collection 名字:List 用 list,数组用 array,Map 用 key。 - 空集合不要进 foreach,否则 SQL 语法错误。

七、批量插入

<insert id="batchInsert">
    INSERT INTO user(name, age) VALUES
    <foreach collection="list" item="u" separator=",">
        (#{u.name}, #{u.age})
    </foreach>
</insert>

注意:JDBC URL 要加 rewriteBatchedStatements=true 才真正批量;否则 MyBatis 还是一条条发。

八、字段名是关键字

orderdescstatus,SQL 中要加反引号:

SELECT `order`, `desc` FROM t ...

九、update 返回值

MyBatis 的 update 方法返回影响行数,不是主键。自增主键要:

<insert useGeneratedKeys="true" keyProperty="id">

十、枚举映射

默认用 EnumTypeHandler(按 name)或 EnumOrdinalTypeHandler(按序号),都不安全。建议用 @EnumValue(MyBatis-Plus)或自定义 TypeHandler。

实战建议

  • 用 MyBatis-Plus 减少 XML 写法,复杂 SQL 才写 XML。
  • 开启 log-impl=StdOutImpl 打印 SQL,方便排查。
  • 大结果集用 fetchSize 或流式查询,避免 OOM。