使用 MyBatis 遇到过哪些坑?¶
一、#{} 与 ${} 的区别¶
#{}:预编译,?占位符,防 SQL 注入。${}:字符串直接拼接,有 SQL 注入风险。
二、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。但以下情况会失效:
- 不同 SqlSession。
- 同一个 SqlSession 但中间执行了
insert/update/delete。 - 手动调用
clearCache()。 - 配置了
flushCache="true"。
四、二级缓存坑多¶
二级缓存跨 SqlSession,但:
- 只能缓存序列化对象,且会序列化。
- 多表关联时容易脏数据。
- 分布式环境下各节点缓存不一致。
生产环境建议关闭二级缓存,用 Redis。
五、resultType 与 resultMap¶
resultType:按字段名自动映射,适合简单查询。resultMap:自定义映射,处理下划线转驼峰、关联查询。
下划线转驼峰全局配置:
六、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 还是一条条发。
八、字段名是关键字¶
如 order、desc、status,SQL 中要加反引号:
九、update 返回值¶
MyBatis 的 update 方法返回影响行数,不是主键。自增主键要:
十、枚举映射¶
默认用 EnumTypeHandler(按 name)或 EnumOrdinalTypeHandler(按序号),都不安全。建议用 @EnumValue(MyBatis-Plus)或自定义 TypeHandler。
实战建议
- 用 MyBatis-Plus 减少 XML 写法,复杂 SQL 才写 XML。
- 开启
log-impl=StdOutImpl打印 SQL,方便排查。 - 大结果集用
fetchSize或流式查询,避免 OOM。