使用注解方式

MyBatis 注解开发全指南:从基础到高级用法

MyBatis 注解方式提供了一种无需 XML 配置即可编写映射语句的方案,适用于 SQL 逻辑简单、追求代码集中管理的场景。系统梳理注解开发的完整用法,包括基础 CRUD、高级映射、动态 SQL 及最佳实践,帮助你灵活选择 XML 或注解方式。

基础注解:CRUD 操作

MyBatis 提供了 @Insert@Update@Delete@Select 四个核心注解,分别对应 XML 中的 <insert><update><delete><select> 标签,使用简单直接。

1. @Insert:插入数据

public interface UserMapper {
    // 基础插入
    @Insert("INSERT INTO user(username, age, email) VALUES(#{username}, #{age}, #{email})")
    int insertUser(User user);

    // 插入并返回自增主键(等价于 XML 中的 useGeneratedKeys)
    @Insert("INSERT INTO user(username, age) VALUES(#{username}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    int insertUserWithId(User user);
}
  • @Options 用于配置插入策略,useGeneratedKeys=true 启用自增主键,keyProperty 绑定实体类属性,keyColumn 绑定数据库列(可选,默认与属性名一致)。

2. @Update@Delete:更新与删除

public interface UserMapper {
    // 更新用户
    @Update("UPDATE user SET username=#{username}, age=#{age} WHERE id=#{id}")
    int updateUser(User user);

    // 根据 ID 删除
    @Delete("DELETE FROM user WHERE id=#{id}")
    int deleteUserById(Integer id);
}

3. @Select:查询数据

public interface UserMapper {
    // 根据 ID 查询单个用户
    @Select("SELECT id, username, age, email FROM user WHERE id=#{id}")
    User selectById(Integer id);

    // 查询所有用户
    @Select("SELECT id, username, age, email FROM user")
    List<User> selectAll();

    // 多参数查询(需用 @Param 绑定参数名)
    @Select("SELECT * FROM user WHERE username LIKE #{name} AND age > #{minAge}")
    List<User> selectByCondition(@Param("name") String username, @Param("minAge") Integer age);
}
  • 多参数时必须使用 @Param 注解指定参数名,否则 MyBatis 无法映射 #{name} 等占位符。

结果映射注解:处理字段映射

当数据库列名与实体类属性名不一致时,需通过结果映射注解自定义映射关系,核心注解为 @Results@Result@ResultMap

1. @Results@Result:定义映射关系

等价于 XML 中的 <resultMap><result> 标签:

public interface UserMapper {
    @Select("SELECT id, user_name, user_age, create_time FROM user WHERE id=#{id}")
    @Results({
        @Result(id = true, column = "id", property = "id"), // id=true 表示为主键
        @Result(column = "user_name", property = "username"), // 数据库列 -> 实体属性
        @Result(column = "user_age", property = "age"),
        @Result(column = "create_time", property = "createTime", 
                javaType = LocalDateTime.class, // 指定 Java 类型(可选)
                jdbcType = JdbcType.TIMESTAMP) // 指定 JDBC 类型(可选)
    })
    User selectByIdWithMapping(Integer id);
}
  • column:数据库表列名;
  • property:实体类属性名;
  • javaType/jdbcType:解决类型转换问题(如日期类型)。

2. @ResultMap:复用结果映射

当多个方法需要相同的结果映射时,可通过 @ResultMap 复用,避免重复代码:

public interface UserMapper {
    // 定义基础结果映射
    @Results(id = "userResultMap", value = {
        @Result(id = true, column = "id", property = "id"),
        @Result(column = "user_name", property = "username"),
        @Result(column = "user_age", property = "age")
    })
    @Select("SELECT id, user_name, user_age FROM user WHERE id=#{id}")
    User selectById(Integer id);

    // 复用结果映射
    @Select("SELECT id, user_name, user_age FROM user WHERE age > #{age}")
    @ResultMap("userResultMap") // 引用上面定义的 id="userResultMap" 的映射
    List<User> selectByAge(Integer age);
}
  • @Resultsid 属性为映射命名,@ResultMap 通过该 id 引用。

3. 混合 XML 映射:@ResultMap 引用 XML 中的 <resultMap>

当结果映射复杂(如级联查询)时,可在 XML 中定义 <resultMap>,再通过注解引用:

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    <resultMap id="xmlResultMap" type="com.example.model.User">
        <id column="id" property="id"/>
        <result column="user_name" property="username"/>
        <result column="user_age" property="age"/>
    </resultMap>
</mapper>
// UserMapper.java
public interface UserMapper {
    @Select("SELECT id, user_name, user_age FROM user WHERE id=#{id}")
    @ResultMap("com.example.mapper.UserMapper.xmlResultMap") // 全限定名引用 XML 中的 resultMap
    User selectByIdWithXmlMap(Integer id);
}

级联查询注解:处理关联关系

MyBatis 注解提供 @One(一对一)和 @Many(一对多)注解,处理实体间的关联关系,等价于 XML 中的 <association><collection>

1. @One:一对一关联

例如,User 关联 UserInfo(一个用户对应一个详细信息):

public interface UserMapper {
    @Select("SELECT * FROM user WHERE id=#{id}")
    @Results({
        @Result(id = true, column = "id", property = "id"),
        @Result(column = "username", property = "username"),
        // 一对一关联:通过 user_id 查询 UserInfo
        @Result(column = "id", property = "userInfo",
                one = @One(
                    select = "com.example.mapper.UserInfoMapper.selectByUserId", // 关联查询的 Mapper 方法
                    fetchType = FetchType.EAGER // 加载策略:立即加载(默认)/延迟加载
                )
        )
    })
    User selectUserWithInfo(Integer id);
}

// UserInfoMapper.java
public interface UserInfoMapper {
    @Select("SELECT * FROM user_info WHERE user_id=#{userId}")
    UserInfo selectByUserId(Integer userId);
}
  • column = "id":将 user 表的 id 作为参数传递给 selectByUserId 方法;
  • fetchType = FetchType.LAZY:开启延迟加载(需全局配置 lazyLoadingEnabled=true)。

2. @Many:一对多关联

例如,User 关联 Order(一个用户对应多个订单):

public interface UserMapper {
    @Select("SELECT * FROM user WHERE id=#{id}")
    @Results({
        @Result(id = true, column = "id", property = "id"),
        @Result(column = "username", property = "username"),
        // 一对多关联:通过 user_id 查询订单列表
        @Result(column = "id", property = "orders",
                many = @Many(
                    select = "com.example.mapper.OrderMapper.selectByUserId",
                    fetchType = FetchType.LAZY
                )
        )
    })
    User selectUserWithOrders(Integer id);
}

// OrderMapper.java
public interface OrderMapper {
    @Select("SELECT * FROM orders WHERE user_id=#{userId}")
    List<Order> selectByUserId(Integer userId);
}

级联查询注意事项

  • N+1 查询问题:注解方式的级联查询默认会触发 N+1 次 SQL(1 次主查询 + N 次关联查询),性能较差;
  • 优化方案:复杂关联查询建议使用 XML 中的 join 语句 + 结果映射,或通过 @SelectProvider 编写动态 SQL 实现关联查询。

缓存注解:二级缓存配置

MyBatis 注解提供 @CacheNamespace@CacheNamespaceRef 注解,对应 XML 中的 <cache><cache-ref>,用于配置二级缓存。

1. @CacheNamespace:开启当前 Mapper 的二级缓存

// 在 Mapper 接口上添加缓存注解
@CacheNamespace(
    eviction = LruCache.class, // 缓存回收策略(LRU 最近最少使用)
    flushInterval = 60000, // 缓存刷新间隔(60秒)
    size = 1024, // 最大缓存条目
    readWrite = true // 是否可读写(false 为只读)
)
public interface UserMapper {
    // ... 方法定义
}
  • 需确保实体类实现 Serializable 接口(readWrite=true 时需要序列化)。

2. @CacheNamespaceRef:引用其他 Mapper 的缓存

当多个 Mapper 操作同一张表时,可通过该注解共享缓存:

// OrderMapper 引用 UserMapper 的缓存配置
@CacheNamespaceRef(UserMapper.class)
public interface OrderMapper {
    // ... 方法定义
}

动态 SQL 注解:@*Provider 系列注解

对于复杂的动态 SQL(如条件判断、循环),注解方式通过 @SelectProvider@InsertProvider@UpdateProvider@DeleteProvider 实现,原理是通过工具类动态生成 SQL 语句。

1. 基础用法:静态 SQL 生成

public interface UserMapper {
    // 引用 UserProvider 类的 selectById 方法生成 SQL
    @SelectProvider(type = UserProvider.class, method = "selectByIdSql")
    User selectById(Integer id);
}

// SQL 生成工具类
public class UserProvider {
    // 方法返回值为 SQL 字符串
    public String selectByIdSql(Integer id) {
        return "SELECT id, username, age FROM user WHERE id = " + id;
    }
}

2. 动态 SQL 生成:条件拼接

使用 MyBatis 提供的 SQL 类(构建器)简化 SQL 拼接:

public interface UserMapper {
    // 动态查询:根据条件拼接 SQL
    @SelectProvider(type = UserProvider.class, method = "selectByCondition")
    List<User> selectByCondition(
        @Param("username") String username,
        @Param("minAge") Integer minAge,
        @Param("maxAge") Integer maxAge
    );
}

public class UserProvider {
    public String selectByCondition(Map<String, Object> params) {
        // 使用 SQL 构建器
        return new SQL() {{
            SELECT("id, username, age");
            FROM("user");
            
            // 条件判断:若 username 不为空,添加 LIKE 条件
            if (params.get("username") != null) {
                WHERE("username LIKE CONCAT('%', #{username}, '%')");
            }
            // 若 minAge 不为空,添加 >= 条件
            if (params.get("minAge") != null) {
                WHERE("age >= #{minAge}");
            }
            // 若 maxAge 不为空,添加 <= 条件
            if (params.get("maxAge") != null) {
                WHERE("age <= #{maxAge}");
            }
        }}.toString();
    }
}
  • SQL 构建器的方法(SELECTFROMWHERE)支持链式调用,自动处理空格和关键字拼接;
  • 多参数时,工具类方法参数建议使用 Map<String, Object>,通过 params.get("key") 获取参数。

3. 批量操作:动态生成批量插入 SQL

public interface UserMapper {
    // 批量插入
    @InsertProvider(type = UserProvider.class, method = "batchInsert")
    int batchInsert(@Param("users") List<User> users);
}

public class UserProvider {
    public String batchInsert(Map<String, Object> params) {
        List<User> users = (List<User>) params.get("users");
        // 构建批量插入 SQL
        SQL sql = new SQL() {{
            INSERT_INTO("user");
            VALUES("username, age", "#{user.username}, #{user.age}");
        }};
        
        // 拼接多个 VALUES 子句
        String baseSql = sql.toString();
        StringBuilder sb = new StringBuilder(baseSql);
        for (int i = 1; i < users.size(); i++) {
            sb.append(", (#{users[").append(i).append("].username}, #{users[").append(i).append("].age})");
        }
        return sb.toString();
    }
}

注解 vs XML:如何选择?

场景 推荐方式 理由
简单 CRUD 操作 注解方式 代码集中,无需切换文件,开发效率高
复杂动态 SQL XML 方式 XML 标签(ifforeach 等)更直观,维护性好
复杂结果映射(级联) XML 方式 XML 的 <resultMap> 支持更丰富的映射配置,避免注解代码冗长
多数据库适配 XML 方式 支持 <databaseIdProvider> 标签,注解方式需通过 Provider 类硬编码处理
团队协作 统一方式 避免混合使用导致风格混乱,大型项目建议以 XML 为主(可读性强)

最佳实践

  1. 避免过度使用注解:复杂 SQL 用 XML,简单 SQL 用注解,平衡开发效率与可维护性;
  2. 参数绑定规范:多参数必须用 @Param 注解,明确参数名,避免 arg0param1 等模糊命名;
  3. 动态 SQL 工具类:使用 SQL 构建器生成动态 SQL,避免字符串拼接错误;
  4. 级联查询优化:注解方式的级联查询(@One/@Many)易导致 N+1 问题,优先使用 JOIN 语句;
  5. 缓存配置:二级缓存建议在 XML 中配置(更直观),注解方式适合简单场景。

总结

MyBatis 注解方式为简单 SQL 操作提供了便捷的解决方案,通过 @Select@Insert 等基础注解可快速实现 CRUD,配合 @Results@One@Many 可处理结果映射和关联查询。但对于复杂动态 SQL 和级联关系,XML 方式仍具有不可替代的优势