← 返回题目列表

MyBatis 的注解 SQL(@Select)和 XML 映射,各有什么优劣、该怎么选?

简单 第 20 / 24 题 更新于 2026/07/28
注解SQLXML映射MapperMyBatis

简化版

MyBatis 写 SQL 有两种方式:① 注解式(在 Mapper 接口方法上标 @Select/@Insert/@Update/@Delete,SQL 直接写在注解里);② XML 式(在 Mapper XML 文件里写 <select> 等,接口只声明方法)。两者功能底层一致(都被解析成 MappedStatement),区别在「SQL 写在哪、适合什么复杂度」:注解式——简单、直观、SQL 和方法在一起,适合简单的单表 CRUD;但动态 SQL 很难写(要用 <script>@SelectProvider 拼 SQL,很别扭),复杂 SQL、长 SQL 挤在注解里可读性差。XML 式——动态 SQL 强大<if>/<foreach>/<where> 等标签)、复杂 SQL 清晰、SQL 和代码分离便于 DBA 维护,适合复杂查询;缺点是要维护 XML 文件、SQL 和方法分两处。主流实践简单 CRUD 用注解,复杂/动态 SQL 用 XML——甚至同一个 Mapper 接口,简单方法用注解、复杂方法在 XML 里写,两者共存。MyBatis-Plus 则让简单单表 CRUD 连注解都不用写。

详细版

注解式 vs XML 式对比

维度注解式(@Select 等)XML 式(
SQL 位置接口方法注解里XML 文件里
简单 SQL✅ 简洁直观稍繁琐(要写 XML)
动态 SQL❌ 别扭(@Provider/ ✅ 强大(<if>/<foreach>)
复杂/长 SQL❌ 挤在注解里难读✅ 清晰、可格式化
SQL 与代码在一起(好找)分离(利于 DBA 维护)
复用(sql 片段)/
结果映射简单场景 @Results✅ resultMap 强大
// 注解式:简单 CRUD 很方便
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    @Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
}

// 注解式写动态 SQL 就别扭了(要用 Provider):
@SelectProvider(type = UserSqlProvider.class, method = "buildQuery")
List<User> query(UserQuery q);   // SQL 拼接逻辑另写在 UserSqlProvider 里
<!-- XML 式:动态 SQL 清晰强大 -->
<select id="query" resultType="User">
  SELECT * FROM user
  <where>
    <if test="name != null">AND name LIKE #{name}</if>
    <if test="minAge != null">AND age >= #{minAge}</if>
  </where>
</select>

⚠️ 别陷入「注解 vs XML 二选一」的思维——它们可以、也应该在一个 Mapper 里混用。同一个 Mapper 接口,简单方法(按 id 查、插入一条)用注解 @Select/@Insert 直接写、清爽;一旦遇到需要动态条件(<if>)、<foreach> 批量、复杂 resultMap 关联映射的方法,就在对应的 XML 里写,接口只声明方法(靠 namespace + 方法名对应)。判断标准是「SQL 的复杂度」,不是「团队规定只能用一种」。历史上很多团队「一律用 XML」是因为早期动态 SQL 只能靠 XML,且便于 DBA 集中审 SQL;而现在有了 MyBatis-Plus,简单单表 CRUD 连 SQL 都不用写(BaseMapper),所以趋势是「单表 CRUD 交给 MP、复杂查询用 XML、零散简单查询用注解」三者按复杂度分工。

完整版教学

一、两种方式底层一致

先建立一个关键认知:注解和 XML 底层是一回事

不管你用 @Select 还是 <select>,MyBatis 都把它解析成:
  一个 MappedStatement(一条 SQL 的完整元数据)
  注册到 Configuration 里

所以:
  - 功能能力上,两者底层一致(都能执行 SQL、映射结果)
  - 区别只在"SQL 写在哪、表达复杂 SQL 方不方便"
  - 不是"两套引擎",是"同一引擎的两种配置入口"

类比:
  Spring 的 XML 配置 Bean 和 注解配置 Bean
  → 底层都变成 BeanDefinition,只是配置入口不同

理解这点,就不会觉得"注解和 XML 是完全不同的东西"
  它们只是"写 SQL 的两种入口",选择看便利性

关键认知:注解和 XML 底层一致——不管 @Select 还是 <select>,都被解析成 MappedStatement 注册到 Configuration(见 MappedStatement 题)。所以功能能力上两者一致,区别只在「SQL 写在哪、表达复杂 SQL 方不方便」,不是两套引擎、而是同一引擎的两种配置入口(类比 Spring XML 配 Bean vs 注解配 Bean 都变 BeanDefinition)。理解「注解和 XML 底层都解析成 MappedStatement、功能一致、只是写 SQL 的两种入口、选择看便利性」,就不会把它们当成完全不同的东西。

二、注解式的优势:简单直观

注解式的优势在「简单场景的直观和便利」:

注解式的好处:
  ① SQL 和方法在一起——看接口就知道这个方法执行什么 SQL
     不用在接口和 XML 之间来回跳
  ② 简单 CRUD 很清爽:
     @Select("SELECT * FROM user WHERE id=#{id}")
     User selectById(Long id);
     → 一目了然,不用维护单独的 XML
  ③ 少一个文件——不用创建和维护 Mapper XML
  ④ 重构友好——改方法名时 SQL 跟着走(在同一处)

适合的场景:
  - 简单的单表 CRUD(按主键查、插入、更新单条)
  - SQL 短、固定(没有动态条件)
  - 小项目、快速开发

一句话:简单 SQL 用注解,省事直观

注解式的优势是「简单场景的直观便利」——SQL 和方法在一起(看接口就知道执行什么 SQL)、简单 CRUD 清爽、少一个 XML 文件、重构友好。适合简单单表 CRUD(按主键查/插入/更新单条)、SQL 短且固定、小项目快速开发。理解「注解式优势:SQL 和方法在一起直观/简单 CRUD 清爽/少维护 XML、适合简单固定的单表 SQL」,就理解了注解式的定位。

三、注解式的短板:动态 SQL

注解式的最大短板是「动态 SQL 写起来很别扭」:

动态 SQL 的需求(根据条件拼不同 SQL):
  查询条件可能有 name、可能有 age、可能都有、可能都没有
  → SQL 的 WHERE 部分要根据参数动态变化

XML 里很优雅(<if>):
  <where>
    <if test="name!=null">AND name=#{name}</if>
    <if test="age!=null">AND age=#{age}</if>
  </where>

注解里就别扭了,两种办法都不好:
  办法一:@Select 里用 <script> 标签包裹动态 SQL
    @Select("<script>SELECT * FROM user <where>" +
            "<if test='name!=null'>AND name=#{name}</if>" +
            "</where></script>")
    → 字符串拼接 XML,可读性差、易出错、没格式化
  办法二:用 @SelectProvider 把 SQL 拼接逻辑写到另一个类
    @SelectProvider(type=Provider.class, method="build")
    → SQL 逻辑和方法分离到 Provider 类,绕、难维护

所以:需要动态 SQL 时,注解式很别扭 → 该用 XML

注解式的最大短板是「动态 SQL 别扭」——需要根据条件拼不同 SQL 时,XML 用 <if> 很优雅,注解里两种办法都不好:@Select 里用 <script> 标签(字符串拼 XML,可读性差易错);@SelectProvider(SQL 拼接逻辑写到另一个类,绕、难维护)。所以需要动态 SQL 时该用 XML。理解「注解式动态 SQL 别扭:

四、XML 式的优势:动态与复杂

XML 式的优势在「动态 SQL 和复杂 SQL」:

XML 式的强项:
  ① 动态 SQL 标签强大:
     <if> 条件、<where>/<set> 智能去 AND/逗号、
     <foreach> 遍历(批量插入、IN 查询)、<choose> 分支、<trim>
     → 复杂动态查询表达清晰

  ② 复杂/长 SQL 可读:
     多表 join、子查询、复杂聚合 → XML 里能格式化、缩进、加注释
     (注解里长 SQL 挤成一行字符串,没法看)

  ③ SQL 片段复用:
     <sql id="baseColumns">id, name, age</sql>
     <include refid="baseColumns"/>
     → 公共 SQL 片段抽取复用

  ④ 强大的 resultMap:
     复杂结果映射、association(一对一)、collection(一对多)
     鉴别器 discriminator → XML 里配置清晰

  ⑤ SQL 与 Java 代码分离:
     便于 DBA 集中审查/优化 SQL,不用翻 Java 代码

适合:复杂查询、动态条件、多表关联、需要 SQL 复用/DBA 维护

XML 式的优势在「动态和复杂 SQL」:① 动态 SQL 标签强大<if>/<where>/<foreach>/<choose>);② 复杂/长 SQL 可读(能格式化缩进加注释,注解里挤成一行没法看);③ SQL 片段复用<sql>/<include>);④ 强大的 resultMap(关联映射 association/collection、鉴别器);⑤ SQL 与代码分离(便于 DBA 审查优化)。适合复杂查询、动态条件、多表关联、需要复用/DBA 维护。理解「XML 优势:动态 SQL 标签强大/复杂长 SQL 可读/SQL 片段复用/强大 resultMap/SQL 代码分离便于 DBA、适合复杂查询和动态条件」,就理解了 XML 式的定位。

五、混用与选择原则

关键实践是「按复杂度混用」,而非二选一:

最佳实践:同一 Mapper 接口里,注解和 XML 混用
  接口:
    @Select("SELECT * FROM user WHERE id=#{id}")  ← 简单方法用注解
    User selectById(Long id);
    List<User> complexQuery(UserQuery q);          ← 复杂方法只声明,SQL 在 XML

  XML(对应 complexQuery):
    <select id="complexQuery" resultType="User">
      ...动态 SQL...
    </select>

  → 靠 namespace(接口全名) + 方法名 对应
  → 一个方法要么在注解里、要么在 XML 里(同一方法别两处都写,冲突)

选择原则(按 SQL 复杂度):
  简单单表 CRUD、固定 SQL     → 注解(@Select 等)
  动态条件、多表、复杂、长 SQL → XML
  完全的单表 CRUD             → MyBatis-Plus 的 BaseMapper(连 SQL 都不写)

避免:
  ✗ 团队强制"只能用一种"(该用 XML 的硬塞注解,或反之)
  ✗ 同一个方法在注解和 XML 都定义(冲突报错)

关键实践是「按复杂度混用」而非二选一——同一 Mapper 接口,简单方法用注解、复杂方法在 XML(靠 namespace+方法名对应,同一方法别两处都写会冲突)。选择原则:简单固定 SQL 用注解、动态/多表/复杂/长 SQL 用 XML、完全单表 CRUD 用 MyBatis-Plus 的 BaseMapper(连 SQL 都不写)。避免「团队强制只用一种」或「同一方法两处定义冲突」。理解「按 SQL 复杂度混用:简单用注解/复杂用 XML/单表 CRUD 用 MP、同一方法别两处写、避免强制只用一种」,就掌握了选择原则。

六、历史与趋势

理解「为什么很多团队一律用 XML」以及现在的趋势:

历史(为什么很多老项目一律 XML):
  ① 早期 MyBatis(iBatis)动态 SQL 只能在 XML 里写
     → 想用动态 SQL 就得 XML
  ② SQL 集中在 XML,便于 DBA 集中审查、优化
     (生产 SQL 是性能命脉,DBA 要盯着)
  ③ 团队规范统一,避免风格混乱
  → 所以形成了"一律 XML"的传统

趋势(现在的分工):
  ① 简单单表 CRUD → MyBatis-Plus BaseMapper(零 SQL)
     大幅减少了"简单 SQL"的量
  ② 复杂查询 → XML(动态 SQL、复杂映射还是 XML 强)
  ③ 零散的简单查询 → 注解(顺手)
  → "MP 兜底单表 + XML 管复杂 + 注解管零散" 三者分工

结论:
  没有绝对的对错,按"SQL 复杂度 + 团队习惯 + 是否用 MP"综合选
  核心判断:这条 SQL 复杂吗?要动态吗?→ 决定注解还是 XML

历史上很多团队「一律用 XML」,因为早期动态 SQL 只能 XML、SQL 集中便于 DBA 审查、团队规范统一。现在的趋势是「三者分工」:简单单表 CRUD 用 MyBatis-Plus(零 SQL)、复杂查询用 XML、零散简单查询用注解。没有绝对对错,按「SQL 复杂度 + 团队习惯 + 是否用 MP」综合选,核心判断是「这条 SQL 复杂吗?要动态吗?」。理解「历史上一律 XML(早期动态 SQL 只能 XML+DBA 审查+规范);趋势三者分工:MP 管单表 CRUD/XML 管复杂/注解管零散、按复杂度选」,就理解了实践的演变和现状。

记忆钩子:「MyBatis 写 SQL 两种方式:注解式(@Select 等,SQL 在接口方法注解里)vs XML 式(,都被解析成 MappedStatement 注册到 Configuration;功能能力一样,区别只在「SQL 写在哪、表达复杂 SQL 方不方便」,是同一引擎的两种配置入口。

  • 误区:只能用注解或只能用 XML,不能混。 恰恰应该混用——同一个 Mapper 接口,简单方法用注解、复杂方法在 XML 里写(靠 namespace+方法名对应);判断标准是 SQL 复杂度,不是团队强制统一一种。
  • 误区:注解式也能优雅地写动态 SQL。 很别扭——要么在 @Select 里用
  • 误区:同一个方法可以既写注解又写 XML。 不行——同一个方法在注解和 XML 里都定义会冲突(重复的 statement id);一个方法要么注解、要么 XML,二选一。
  • 追问:什么样的 SQL 适合用注解,什么样适合用 XML? 简单、固定、单表的 SQL(按主键查、插入更新单条)适合注解(直观省事);动态条件()、批量()、多表 join、复杂 resultMap 关联映射、长 SQL 适合 XML(表达清晰、可格式化、便于维护)——判断核心是「SQL 复杂度和是否需要动态」。
  • 追问:MyBatis-Plus 出现后,注解和 XML 的分工有什么变化? MP 的 BaseMapper 让简单单表 CRUD 连 SQL 都不用写,接管了大部分「简单 SQL」;于是分工变成「单表 CRUD 交给 MP、复杂查询用 XML、零散简单查询用注解」三者按复杂度分工,注解和 XML 主要处理 MP 搞不定的定制查询。
  • 追问:注解式怎么做结果映射(复杂一点的)? 用 @Results/@Result 注解(对应 XML 的 resultMap),@Result 指定 column→property 映射;但复杂的关联映射(一对一 @One、一对多 @Many)在注解里写起来繁琐难读,还是 XML 的 resultMap 更清晰强大。
  • 八、加强记忆

    MyBatis 写 SQL 两种方式① 注解式@Select/@Insert/@Update/@Delete,SQL 写在接口方法注解里);② XML 式<select> 等写在 Mapper XML,接口只声明方法)。两者底层一致(都解析成 MappedStatement 注册到 Configuration,是同一引擎的两种配置入口),区别只在「SQL 写在哪、表达复杂 SQL 方不方便」。注解式优势:简单直观(SQL 和方法在一起、少维护 XML、重构友好),适合简单固定的单表 CRUD短板:动态 SQL 别扭(<script> 字符串拼 XML 难读,或 @SelectProvider 逻辑分离到另一个类绕)。XML 式优势:动态 SQL 标签强大(<if>/<foreach>/<where>)、复杂长 SQL 可读、<sql>/<include> 片段复用、resultMap 关联映射(association/collection)强大、SQL 与代码分离便于 DBA,适合复杂/动态查询最佳实践:按 SQL 复杂度混用——简单用注解、复杂用 XML、完全单表 CRUD 用 MyBatis-Plus 的 BaseMapper(零 SQL);同一方法别在两处都定义(冲突);判断标准是「SQL 复杂度」不是「团队强制一种」。一句话「注解式(@Select)vs XML 式底层一致(都是 MappedStatement);注解简单直观但动态 SQL 别扭、XML 动态强大复杂可读片段复用;按复杂度混用:简单注解/复杂 XML/单表 CRUD 用 MP,同一方法别两处写」。