MyBatis-Plus XML 映射

总体原则

能用 MP 自带方法或条件构造器解决的,就绝不写 XML;遇到 MP 无法高效完成的复杂 SQL(如多表联查、复杂嵌套、特殊函数等),才考虑手写 XML。

MyBatis-Plus 的核心目标就是让你少写甚至不写 SQL,但它并不排斥手写 SQL —— 反而鼓励你当工具不够用时,回归 MyBatis 的原生方式(即 XML 或注解)去实现。


✅ 完全不需要写 XML 的情况(占日常开发的 80% 以上)

1. 单表的普通增删改查

  • 插入一条记录:insert(T entity)
  • 根据 ID 删除:deleteById(Serializable id)
  • 根据 ID 修改:updateById(T entity)
  • 根据 ID 查询:selectById(Serializable id)
  • 查询全部:selectList(null)

这些操作 MP 的 BaseMapper 已经完整提供,一行代码都不用写 XML。

2. 单表带条件的查询、更新、删除

使用 LambdaQueryWrapperLambdaUpdateWrapper 即可完成任何单表条件操作。
例如:

  • 条件查询:selectList(wrapper)
  • 条件分页查询:selectPage(page, wrapper)
  • 条件更新:update(entity, wrapper)
  • 条件删除:delete(wrapper)

示例:查询年龄大于 18 且名字包含“张”的用户,按年龄降序

java

1
2
3
4
5
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
.like(User::getName, "张")
.orderByDesc(User::getAge);
List<User> list = userMapper.selectList(wrapper);

完全不写 SQL,也不需要 XML。

3. 简单的统计聚合(单表)

  • 统计记录数:selectCount(wrapper)
  • 获取某个字段的最大值 / 最小值 / 总和 / 平均值(需要通过 selectObjs 或自定义方法,但 MP 也提供了 selectMaps 配合 wrapper 的 select 子句实现)

虽然 MP 没有直接提供 selectSum 这样的方法,但可以通过 wrapper.select("SUM(age) as totalAge") 配合 selectMaps 实现,仍然不需要 XML。

4. 批量操作(简单版)

MP 的 IService 接口提供了 saveBatchupdateBatchById 等批量方法,底层虽然还是循环执行,但已经封装好了,日常足够使用。

5. 逻辑删除、乐观锁、自动填充等特性

这些通过注解和插件实现,完全无需写 SQL。

6. 大部分分页查询

MP 的分页插件配合 selectPage 方法即可完成物理分页,无需手写 limit 语句。


❌ 必须(或强烈建议)写 XML 的场景

1. 多表关联查询(join)

MP 的 Wrapper 是单表操作的,无法直接 join 其他表。
例如:查询用户信息并关联其订单表,一次性查出 UserOrder 的部分字段。
这种情况只能用 XML 手写 SQL。

示例(XML 片段):

xml

1
2
3
4
5
6
<select id="selectUserWithOrders" resultMap="userOrderMap">
SELECT u.*, o.id as order_id, o.amount
FROM user u
LEFT JOIN order o ON u.id = o.user_id
WHERE u.age > #{age}
</select>

2. 复杂的嵌套查询 / 子查询

虽然 Wrapper 可以支持简单的 inSqlexists 等,但遇到多层嵌套、带 ANY/ALL 或关联子查询时,手写 SQL 更清晰、更可靠。

3. 一对多、多对一、多对多的结果映射

当你要将一个查询结果映射成一个包含集合字段的复杂 Java 对象时(例如一个用户带多个订单),必须使用 XML 的 <collection> 标签,因为 MP 无法自动处理这种映射。

示例

xml

1
2
3
4
<resultMap id="userWithOrders" type="User">
<id column="id" property="id"/>
<collection property="orders" ofType="Order" column="id" select="selectOrdersByUserId"/>
</resultMap>

4. 需要使用数据库特定函数或语法

  • 全文索引(MATCH AGAINST)
  • 窗口函数(ROW_NUMBER, RANK)
  • 递归查询(WITH RECURSIVE)
  • 特定数据库的 JSON 操作函数
  • 存储过程调用

MP 的 Wrapper 可能支持有限,而 XML 可以直接写原生 SQL。

5. 复杂动态 SQL,分支条件非常多

虽然 Wrapper 支持动态条件(通过 condition 参数),但如果你的业务逻辑里需要根据不同的场景拼接完全不同的 SQL 片段(例如多表动态 join),XML 的 <if><choose><foreach> 等标签会更加直观和易于维护。

6. 性能要求极高的批量更新或插入

MP 的 saveBatch 本质是逐条执行,性能不如 XML 的 <foreach> 配合一条 insert ... values (...), (...), (...) 语句。对于批量插入几万条数据,手写 XML 并使用 JDBC 的 rewriteBatchedStatements 更优。

7. 多表联合更新 / 删除

MP 的 updatedelete 方法只作用于单表。如果需要 UPDATE table1 JOIN table2 ON ... 或者 DELETE t1 FROM t1 LEFT JOIN t2 ...,必须手写 SQL。

8. 需要复用复杂 SQL 片段

XML 可以使用 <sql><include> 来复用 SQL 片段,避免重复代码,MP 的 Wrapper 无法做到。

9. 自定义返回结果类型(非实体类)

例如,你需要返回一个统计报表 Map<String, Object> 或者一个 VO 类,且字段来自多张表,XML 可以直接定义 resultTyperesultMap,使用方便。


📌 混合使用的建议方案

MP 允许你在同一个 Mapper 接口中,既继承 BaseMapper,又添加自定义方法。自定义方法的 SQL 写在对应的 XML 文件中。

java

1
2
3
4
5
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 这个方法使用 XML 写复杂 SQL
List<UserVO> selectUserWithOrders(@Param("age") Integer age);
}

然后在 UserMapper.xml 中实现该方法。
其他简单操作(如 insertselectById)直接通过继承的 BaseMapper 使用,无需在 XML 中重复定义。


🧠 快速判断法则(一句话)

只要你的 SQL 只涉及一张表,且不需要特殊的结果映射或数据库专有语法,就优先用 MP 的 Wrapper;一旦出现多表、复杂子查询、特殊函数或一对多集合映射,就果断切换到 XML。


总结表格

操作类型是否需要 XML推荐方式
单表增删改查(根据 ID)不需要BaseMapper 自带方法
单表条件查询 / 更新 / 删除(包括分页、排序、逻辑删除)不需要LambdaQueryWrapper + BaseMapper
单表统计(count、sum、avg 等)可选(推荐用 wrapper)wrapper.select("SUM(age)") + selectMaps
多表关联查询(JOIN)必须自定义 XML 方法 + <resultMap>
多表联合更新 / 删除必须手写 SQL 在 XML 中
一对多 / 多对一 结果映射必须XML <collection> / <association>
复杂子查询 / 嵌套查询视复杂度而定简单可尝试 inSql,复杂时写 XML
使用数据库特有函数(窗口函数、递归 CTE、JSON 函数等)必须手写 SQL
高批量插入(几万条)建议XML + <foreach> 或 使用 rewriteBatchedStatements
存储过程调用必须XML 中写 {call proc_name(...)}
动态 SQL 分支非常复杂(多表、可选 join)建议XML 的 <if><choose> 更清晰