MyBatis 的注解式开发是除了 XML 映射之外的另一种主流方式。它把 SQL 直接写在 Java 接口的方法上,省去了 XML 文件,适合简单查询和快速开发。
下面我从最基础的注解开始,逐步深入到复杂的映射和动态 SQL
一、为什么用注解式开发?
| 维度 | 注解式 | XML 式 |
|---|---|---|
| 代码密度 | 高(SQL 和 Java 在同一文件) | 低(需要单独维护 XML) |
| 可读性 | 简单查询清晰 | 复杂查询更易读 |
| 动态 SQL | 较复杂(用@SelectProvider) | 非常灵活(<if>、<where>) |
| 维护成本 | 低(修改接口即改 SQL) | 中等(需同时维护接口和 XML) |
| 适用场景 | 简单 CRUD、快速开发 | 复杂联表查询、动态条件多 |
结论:没有绝对的优劣,实际项目中通常是注解和 XML 混合使用,简单查询用注解,复杂查询用 XML。
二、基础 CRUD 注解
1.@Select/@Insert/@Update/@Delete
publicinterfaceUserMapper{// 查询单条@Select("SELECT * FROM user WHERE id = #{id}")UserselectById(Longid);// 查询列表@Select("SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')")List<User>selectByName(Stringname);// 插入,使用 @Options 返回自增主键@Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})")@Options(useGeneratedKeys=true,keyProperty="id")intinsert(Useruser);// 更新@Update("UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}")intupdate(Useruser);// 删除@Delete("DELETE FROM user WHERE id = #{id}")intdeleteById(Longid);}2. 返回自增主键
使用@Options(useGeneratedKeys = true, keyProperty = "id"),插入后 MyBatis 会自动把生成的主键设置到对象的id属性中。
Useruser=newUser();user.setName("张三");userMapper.insert(user);System.out.println("新插入的ID:"+user.getId());// 自动回填三、参数传递
1. 单个参数(基本类型)
直接用#{参数名}即可。
@Select("SELECT * FROM user WHERE id = #{id}")UserselectById(Longid);2. 多个参数——使用@Param
多个参数时,必须用@Param注解给参数起别名。
@Select("SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%') AND age > #{age}")List<User>selectByNameAndAge(@Param("name")Stringname,@Param("age")Integerage);如果没有@Param,MyBatis 会用arg0、arg1或者param1、param2作为参数名,非常不直观。
3. 传入 POJO 对象
直接用#{属性名}即可,MyBatis 会自动读取对象的 getter 方法。
@Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})")intinsert(Useruser);4. 传入 Map
@Select("SELECT * FROM user WHERE name = #{name} AND age = #{age}")List<User>selectByMap(Map<String,Object>params);调用时:
Map<String,Object>map=newHashMap<>();map.put("name","张三");map.put("age",25);userMapper.selectByMap(map);四、结果映射——@Results和@Result
当数据库字段名和 Java 属性名不一致时,需要手动映射。
@Select("SELECT id, user_name, user_age FROM user WHERE id = #{id}")@Results(id="userResultMap",value={@Result(column="id",property="id"),@Result(column="user_name",property="name"),@Result(column="user_age",property="age")})UserselectById(Longid);@Results的id属性可以给这个映射起一个名字,在其他方法中复用。
@Select("SELECT id, user_name, user_age FROM user")@ResultMap("userResultMap")// 复用上面的映射List<User>selectAll();五、复杂关联查询——联表映射
1. 一对一关联(@One)
publicclassUser{privateLongid;privateStringname;privateDeptdept;// 所属部门}// 查询用户时,顺便查询部门信息@Select("SELECT id, name, dept_id FROM user WHERE id = #{id}")@Results({@Result(column="id",property="id"),@Result(column="name",property="name"),@Result(column="dept_id",property="dept",one=@One(select="com.example.mapper.DeptMapper.selectById"))})UserselectUserWithDept(Longid);2. 一对多关联(@Many)
publicclassDept{privateLongid;privateStringname;privateList<User>users;// 部门下的所有用户}@Select("SELECT * FROM dept WHERE id = #{id}")@Results({@Result(column="id",property="id"),@Result(column="name",property="name"),@Result(column="id",property="users",many=@Many(select="com.example.mapper.UserMapper.selectByDeptId"))})DeptselectDeptWithUsers(Longid);六、动态 SQL——@SelectProvider
注解方式处理动态 SQL(<if>、<where>等)比较麻烦,需要用@SelectProvider、@InsertProvider等注解。
publicclassUserSqlProvider{publicStringselectByCondition(Map<String,Object>params){Stringname=(String)params.get("name");Integerage=(Integer)params.get("age");SQLsql=newSQL(){{SELECT("*");FROM("user");if(name!=null&&!name.isEmpty()){WHERE("name LIKE CONCAT('%', #{name}, '%')");}if(age!=null){WHERE("age > #{age}");}ORDER_BY("id DESC");}};returnsql.toString();}}// Mapper 中使用@SelectProvider(type=UserSqlProvider.class,method="selectByCondition")List<User>selectByCondition(Map<String,Object>params);注意:@SelectProvider的method方法返回值必须是 String(即 SQL 语句)。
七、常用注解汇总
| 注解 | 用途 |
|---|---|
@Select | 查询 |
@Insert | 插入 |
@Update | 更新 |
@Delete | 删除 |
@Options | 配置选项(如自增主键返回) |
@Param | 参数命名 |
@Results/@Result | 结果字段映射 |
@One | 一对一关联查询 |
@Many | 一对多关联查询 |
@ResultMap | 复用已定义的@Results |
@SelectProvider | 动态 SQL 提供者 |
@InsertProvider/@UpdateProvider/@DeleteProvider | 同上 |
八、注解 vs XML:如何选择?
| 场景 | 推荐方式 |
|---|---|
| 单表 CRUD | 注解 |
| 简单联表查询(2-3张表) | 注解 |
复杂动态 SQL(多个<if>、<foreach>) | XML |
| 5张表以上的复杂联表 | XML |
需要复用 SQL 片段(<sql>) | XML |
| 快速原型开发 | 注解 |
| 大型项目、长期维护 | 混合(注解+XML) |
九、混合使用的配置方式
如果同时使用注解和 XML,MyBatis 会优先使用 XML(如果接口方法和 XML 中的 id 相同,XML 会覆盖注解)。更好的做法是明确区分:
- 简单查询:注解写在接口方法上
- 复杂查询:XML 写在对应的 Mapper XML 文件中,接口中只声明方法,不加注解
// 接口中不写 @Select,完全由 XML 接管publicinterfaceUserMapper{List<User>selectComplex(Map<String,Object>params);}<!-- XML 文件中定义复杂 SQL --><selectid="selectComplex"resultType="User">SELECT * FROM user<where><iftest="name != null">name LIKE CONCAT('%', #{name}, '%')</if><!-- 更多复杂条件 --></where></select>十、最佳实践建议
- 简单 CRUD 用注解,复杂查询用 XML,不要把注解搞得过于复杂。
@Results一定要复用,通过id和@ResultMap避免重复定义。- 动态 SQL 超过 5 行时,果断用 XML,可读性更好。
@Param不要省,哪怕只有一个参数也养成习惯,方便后期扩展。@SelectProvider适合复杂动态 SQL,但 SQL 构造逻辑建议单独放在一个类里,保持 Mapper 接口的干净。