MyBatis 的注解 SQL(@Select)和 XML 映射,各有什么优劣、该怎么选?
简化版
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,同一方法别两处写」。