Skip to content

MyBatis 注解方式 vs XML 方式对比

MyBatis 提供了两种定义 SQL 的方式:

  • 注解方式:SQL 直接写在 Java 接口方法上
  • XML 方式:SQL 写在独立的 XML 文件中

选哪个?

这是一个老生常谈的问题,但答案并不是非此即彼。

快速对比

┌─────────────────────────────────────────────────────────────────────────┐
│                         MyBatis SQL 定义方式                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  注解方式:                        XML 方式:                          │
│  ┌─────────────────────┐           ┌─────────────────────────────┐      │
│  │ @Select("...")     │           │ <select id="..." resultMap │      │
│  │ @Insert("...")     │           │   <result.../>              │      │
│  │ @Update("...")     │           │ </select>                   │      │
│  │ @Delete("...")     │           └─────────────────────────────┘      │
│  │ @Results({...})    │                                              │
│  └─────────────────────┘                                              │
│                                                                         │
│  代码在一起                      代码分离                               │
│  简单 SQL 方便                   复杂 SQL 清晰                         │
│  动态 SQL 困难                   动态 SQL 强大                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

注解方式

基本 CRUD

java
@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);
    
    @Insert("INSERT INTO user (name, email, age) VALUES (#{name}, #{email}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    @Update("UPDATE user SET name = #{name}, email = #{email} WHERE id = #{id}")
    int update(User user);
    
    @Delete("DELETE FROM user WHERE id = #{id}")
    int delete(Long id);
}

动态 SQL(注解方式)

使用 <script> 标签包裹动态 SQL:

java
@Mapper
public interface UserMapper {
    
    // 动态条件查询
    @Select("<script>" +
            "SELECT * FROM user WHERE 1=1" +
            "<if test='name != null'> AND name LIKE CONCAT('%', #{name}, '%')</if>" +
            "<if test='age != null'> AND age = #{age}</if>" +
            "<if test='status != null'> AND status = #{status}</if>" +
            "</script>")
    List<User> selectByConditions(User user);
    
    // 动态插入
    @Insert("<script>" +
            "INSERT INTO user" +
            "<trim prefix='(' suffix=')' suffixOverrides=','>" +
            "<if test='name != null'>name,</if>" +
            "<if test='email != null'>email,</if>" +
            "<if test='age != null'>age,</if>" +
            "</trim>" +
            "VALUES" +
            "<trim prefix='(' suffix=')' suffixOverrides=','>" +
            "<if test='name != null'>#{name},</if>" +
            "<if test='email != null'>#{email},</if>" +
            "<if test='age != null'>#{age},</if>" +
            "</trim>" +
            "</script>")
    int insertSelective(User user);
    
    // 批量操作
    @Insert("<script>" +
            "INSERT INTO user (name, email) VALUES" +
            "<foreach collection='list' item='item' separator=','>" +
            "(#{item.name}, #{item.email})" +
            "</foreach>" +
            "</script>")
    int batchInsert(@Param("list") List<User> users);
}

结果映射

java
@Mapper
public interface UserMapper {
    
    // 使用 @Results 指定映射
    @Select("SELECT * FROM user WHERE id = #{id}")
    @Results({
        @Result(property = "id", column = "id"),
        @Result(property = "name", column = "user_name"),
        @Result(property = "email", column = "email"),
        @Result(property = "createTime", column = "create_time")
    })
    User selectById(Long id);
    
    // 一对一关联
    @Select("SELECT u.*, o.id as order_id, o.order_no " +
            "FROM user u LEFT JOIN orders o ON u.id = o.user_id " +
            "WHERE u.id = #{id}")
    @Results({
        @Result(property = "id", column = "id"),
        @Result(property = "name", column = "user_name"),
        @Result(property = "order", one = @One(select = "com.example.mapper.OrderMapper.selectById"),
                column = "order_id")
    })
    User selectUserWithOrder(Long id);
    
    // 一对多关联
    @Select("SELECT * FROM user WHERE id = #{id}")
    @Results({
        @Result(property = "id", column = "id"),
        @Result(property = "orders", many = @Many(select = "com.example.mapper.OrderMapper.selectByUserId"),
                column = "id")
    })
    User selectUserWithOrders(Long id);
}

XML 方式

基本 CRUD

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UserMapper">
    
    <select id="selectById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (name, email, age) VALUES (#{name}, #{email}, #{age})
    </insert>
    
    <update id="update">
        UPDATE user
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="email != null">email = #{email},</if>
        </set>
        WHERE id = #{id}
    </update>
    
    <delete id="delete">
        DELETE FROM user WHERE id = #{id}
    </delete>
    
</mapper>

动态 SQL

xml
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 条件查询 -->
    <select id="selectByConditions" resultType="User">
        SELECT * FROM user
        <where>
            <if test="name != null">
                AND name LIKE CONCAT('%', #{name}, '%')
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
            <if test="status != null">
                AND status = #{status}
            </if>
        </where>
        <if test="orderBy != null">
            ORDER BY ${orderBy}
        </if>
    </select>
    
    <!-- 批量插入 -->
    <insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (name, email, age) VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.name}, #{item.email}, #{item.age})
        </foreach>
    </insert>
    
    <!-- 批量更新 -->
    <update id="batchUpdate">
        <foreach collection="list" item="item" separator=";">
            UPDATE user
            <set>
                <if test="item.name != null">name = #{item.name},</if>
                <if test="item.status != null">status = #{item.status},</if>
            </set>
            WHERE id = #{item.id}
        </foreach>
    </update>
    
    <!-- choose 实现多条件单选 -->
    <select id="selectByStatus" resultType="User">
        SELECT * FROM user
        <choose>
            <when test="status == 1">WHERE status = 1</when>
            <when test="status == 0">WHERE status = 0</when>
            <otherwise>WHERE status IN (0, 1)</otherwise>
        </choose>
    </select>
    
</mapper>

关联查询

xml
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 一对一 association -->
    <resultMap id="UserWithOrderMap" type="User">
        <id property="id" column="id"/>
        <result property="name" column="user_name"/>
        <association property="order" column="order_id"
                    javaType="Order">
            <id property="id" column="order_id"/>
            <result property="orderNo" column="order_no"/>
        </association>
    </resultMap>
    
    <select id="selectUserWithOrder" resultMap="UserWithOrderMap">
        SELECT u.id, u.name as user_name, o.id as order_id, o.order_no
        FROM user u
        LEFT JOIN orders o ON u.id = o.user_id
        WHERE u.id = #{id}
    </select>
    
    <!-- 一对多 collection -->
    <resultMap id="UserWithOrdersMap" type="User">
        <id property="id" column="id"/>
        <result property="name" column="user_name"/>
        <collection property="orders" ofType="Order" column="id"
                   select="com.example.mapper.OrderMapper.selectByUserId"/>
    </resultMap>
    
    <select id="selectUserWithOrders" resultMap="UserWithOrdersMap">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
</mapper>

详细对比

1. SQL 复杂度

SQL 类型注解方式XML 方式
简单 CRUD✅ 方便✅ 也可以
条件查询⚠️ 可用 script✅ 清晰
动态 SQL⚠️ 可用 script✅ 强大
多表关联⚠️ 可用注解✅ 清晰
批量操作⚠️ 可用 script✅ 清晰
SQL 复用❌ 困难✅ include

2. 可维护性

维度注解方式XML 方式
代码集中✅ 一个类搞定❌ 需要两个文件
SQL 调整⚠️ 需要改代码✅ 只需改 XML
代码审查⚠️ SQL 和代码混在一起✅ SQL 独立
版本控制⚠️ 和 Java 文件一起✅ 可以独立管理

3. 性能

性能上没有区别。MyBatis 会在启动时解析注解或 XML,生成相同的 SQL 信息。

4. 适用场景

┌─────────────────────────────────────────────────────────────────────────┐
│                         选择建议                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  选择注解方式:                                                          │
│  ✅ 简单 CRUD 操作                                                      │
│  ✅ SQL 逻辑简单,不常修改                                              │
│  ✅ 小型项目或微服务                                                    │
│  ✅ 团队熟悉注解方式                                                    │
│                                                                         │
│  选择 XML 方式:                                                         │
│  ✅ 复杂动态 SQL                                                       │
│  ✅ SQL 经常需要调整                                                    │
│  ✅ 需要复用 SQL 片段                                                   │
│  ✅ 大型项目,SQL 量大                                                  │
│  ✅ 需要精细控制 SQL                                                    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

混用策略

最佳实践:简单 SQL 用注解,复杂 SQL 用 XML

java
@Mapper
public interface UserMapper {
    
    // 简单 SQL 用注解
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);
    
    @Insert("INSERT INTO user (name, email) VALUES (#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    // 复杂 SQL 引用 XML
    List<User> selectByConditions(UserQuery query);
    
    // 批量操作
    int batchUpdate(List<User> users);
}

对应的 XML 文件:

xml
<mapper namespace="com.example.mapper.UserMapper">
    
    <select id="selectByConditions" resultType="User">
        <!-- 复杂条件查询 -->
    </select>
    
    <update id="batchUpdate">
        <!-- 批量更新 -->
    </update>
    
</mapper>

常见问题

1. 注解方式如何实现 SQL 复用?

java
// 使用 @SelectProvider
@SelectProvider(type = UserSqlProvider.class, method = "buildSelectSql")
List<User> selectByConditions(UserQuery query);

// SQL Provider 类
public class UserSqlProvider {
    public String buildSelectSql(UserQuery query) {
        return new SQL() {{
            SELECT("*");
            FROM("user");
            if (query.getName() != null) {
                WHERE("name LIKE CONCAT('%', #{name}, '%')");
            }
            if (query.getAge() != null) {
                WHERE("age = #{age}");
            }
        }}.toString();
    }
}

2. XML 中如何引用注解定义的 SQL?

xml
<!-- 在 XML 中引用 -->
<select id="selectWithCount" resultType="map">
    SELECT u.*,
           (<include refid="com.example.mapper.UserMapper.selectCount"/>) as user_count
    FROM user u
</select>

3. 注解和 XML 同时存在?

XML 会覆盖注解,MyBatis 会优先使用 XML 中定义的 SQL。


面试追问

Q1: MyBatis 中 #{} 和 ${} 的区别?

答:

  • #{}预编译,使用占位符,防止 SQL 注入
  • ${}直接拼接,不预编译,有 SQL 注入风险
java
// #{}:生成 PreparedStatement 参数
SELECT * FROM user WHERE id = ?  // 安全

// ${}:直接替换
SELECT * FROM user ORDER BY ${columnName}  // 可能被注入

最佳实践:能用 #{} 就用 #{},只有在需要 SQL 片段拼接(如 ORDER BY 字段名)时才用 ${}

Q2: 注解方式能完全替代 XML 吗?

答:不能。注解方式的动态 SQL 支持较弱,复杂场景(如多表关联、复杂动态查询)用 XML 更清晰。

Q3: MyBatis 的 SQL 执行流程是什么?

答:

SqlSessionFactoryBuilder

SqlSessionFactory(解析 XML/注解)

SqlSession(执行 SQL)

Executor(执行器)

StatementHandler(SQL 语句处理器)

ParameterHandler(参数处理)

ResultSetHandler(结果处理)

总结

场景推荐方式
单表简单 CRUD注解
动态条件查询XML
多表关联查询XML
批量操作XML
SQL 复杂但不常改注解 + Provider
SQL 简单但常改XML

没有最好的方式,只有最适合的方式。根据团队情况和项目特点选择即可。

基于 VitePress 构建