在MyBatis框架中,传统的XML配置方式虽然功能强大,但在简单CRUD场景下略显繁琐。MyBatis提供的注解开发模式,能够直接在Mapper接口上通过注解定义SQL语句和结果映射规则,大幅简化开发流程。本文将结合完整的代码示例,从核心注解分类、基础CRUD实现、结果集封装、关联查询(一对一/一对多)到延迟加载等关键场景,全面拆解MyBatis注解开发的核心用法,助力开发者快速上手并灵活运用。
一、MyBatis注解开发前置准备:核心配置与环境说明
使用MyBatis注解开发前,需完成两项核心配置:一是在MyBatis核心配置文件中注册Mapper接口;二是确保实体类与数据库表字段的映射关系清晰(可通过注解或默认命名规则匹配)。
1.1 Mapper接口注册(核心配置文件)
MyBatis注解开发无需编写XML映射文件,但需在核心配置文件的<mappers>标签中注册Mapper接口,支持两种注册方式:单个接口注册和包扫描注册(推荐批量注册)。代码示例如下:
XML
<mappers>
<!-- 方式1:单个接口注册,仅引入指定接口 -->
<mapper class="com.dao.UserAnnoDao"/>
<!-- 方式2:包扫描注册,自动扫描指定包下所有Mapper接口(推荐) -->
<package name="com.dao"/>
</mappers>
注意:包扫描注册要求Mapper接口名与实体类名遵循规范(如UserDao对应User实体),且接口所在包路径需与配置的name属性一致。
1.2 环境依赖说明
确保项目中引入MyBatis核心依赖(以Maven为例),同时配置好数据库连接信息(driver、url、username、password),基础环境与XML开发模式一致,无需额外配置。
二、MyBatis核心注解分类与作用
MyBatis注解可分为五大类:SQL操作注解、结果集封装注解、关联查询注解、辅助注解,各类注解的核心作用如下表所示(结合本文代码示例中的注解全覆盖):
| 注解类别 | 核心注解 | 作用说明 |
|---|---|---|
| SQL操作注解 | @Select、@Insert、@Update、@Delete | 对应查询、新增、修改、删除操作,直接在注解中编写SQL语句 |
| 结果集封装注解 | @Result、@Results、@ResultMap | 解决实体类字段与数据库表字段名不一致问题,@Results可封装多个结果映射,@ResultMap复用已定义的结果集 |
| 关联查询注解 | @One、@Many | @One实现一对一/多对一关联封装,@Many实现一对多关联封装 |
| 辅助注解 | @SelectKey、@Options | @SelectKey获取新增数据的自增主键,@Options配置缓存、分页等辅助参数 |
| 延迟加载注解 | FetchType.LAZY | 配合@One/@Many使用,实现关联数据的延迟加载(按需加载) |
三、基础CRUD实现:注解开发实战(UserDao案例)
以User实体类(对应user表)的CRUD操作为例,完整演示SQL操作注解与结果集封装注解的结合使用,覆盖查询所有、按ID查询、新增、修改、删除、统计数量、模糊查询等核心场景。
3.1 实体类与表结构说明
假设user表字段为:id(自增主键)、username(用户名)、birthday(生日)、sex(性别)、address(地址);User实体类字段与表字段一致(若不一致,可通过@Result指定映射关系)。
3.2 UserDao接口完整实现(注解版)
java
public interface UserDao {
// 1. 查询所有用户:使用@Select+@Results封装结果集
@Select("select * from user")
@Results(id="userMap",value = {
@Result(property = "id",column = "id"), // property:实体类字段;column:数据库表字段
@Result(property = "username",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address")
})
public List<User> findAll();
// 2. 按ID查询用户:复用@Results定义的结果集(通过@ResultMap)
@Select("select * from user where id = #{id}")
@ResultMap(value = "userMap") // 复用id为userMap的结果集,避免重复编写
public User findById(int id);
// 3. 新增用户:使用@Insert+@SelectKey获取自增主键
@Insert("insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})")
@SelectKey(
statement="select last_insert_id()", // 查询自增主键的SQL(MySQL)
keyColumn = "id", // 数据库表主键字段
keyProperty = "id", // 实体类对应主键字段
before =false, // 主键生成时机:false=新增后获取
resultType =Integer.class // 主键数据类型
)
public int insert(User user);
// 4. 修改用户
@Update("update user set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}")
public int update(User user);
// 5. 删除用户
@Delete("delete from user where id = #{id}")
public int delete(int id);
// 6. 统计用户数量
@Select("select count(*) from user")
public int findCount();
// 7. 模糊查询(按用户名):使用concat拼接模糊匹配符,避免SQL注入
@Select("select * from user where username like concat('%',#{username},'%')")
public List<User> findByName(String username);
}
3.3 核心注解详解与注意事项
-
@Results与@ResultMap复用:在findAll()方法中,通过@Results定义了id为userMap的结果集,后续findById()方法直接使用@ResultMap(value = "userMap")复用该结果集,减少重复代码。若实体类字段与表字段完全一致,可省略@Results注解,MyBatis会自动完成映射。
-
@SelectKey获取自增主键:MySQL中通过select last_insert_id()查询新增数据的自增主键,before=false表示在数据插入后执行该查询;若为Oracle数据库,需将statement改为"select 序列名.nextval from dual",且before=true(插入前生成主键)。
-
模糊查询防注入:采用concat('%',#{username},'%')拼接模糊匹配符,而非直接写为'%{username}%',避免{}占位符导致的SQL注入风险(#{}会自动进行参数过滤)。
四、关联查询与延迟加载:@One与@Many实战(Teacher-Student案例)
在实际开发中,多表关联查询是常见场景(如教师与学生的一对多关系:一个教师对应多个学生)。MyBatis通过@One(一对一/多对一)和@Many(一对多)注解实现关联数据的封装,配合FetchType.LAZY可实现延迟加载。
4.1 关联关系说明
假设存在teacher表(id、Tname)和student表(id、Sname、t_id),t_id为student表的外键,关联teacher表的id字段,形成"教师-学生"的一对多关系。
4.2 关联查询与延迟加载实现
java
// TeacherDao接口:查询所有教师(延迟加载关联的学生)
public interface TeacherDao {
// 延迟加载:查询所有教师,关联的学生数据按需加载
@Select("select * from Teacher")
@Results(value = {
@Result(property = "id",column = "id"),
@Result(property = "Tname",column = "Tname"),
// @Many实现一对多关联:一个教师对应多个学生
@Result(
property = "students", // Teacher实体类中关联学生的集合字段(List<Student> students)
column = "id", // 关联条件:教师表的id字段
many = @Many(
select = "com.qcby.dao.StudentDao.findByUid", // 关联查询的SQL所在方法
fetchType = FetchType.LAZY // 延迟加载:默认FetchType.EAGER(立即加载)
)
)
})
public List<Teacher> findAllLazy();
}
// StudentDao接口:根据教师ID查询学生
public interface StudentDao {
// 按教师ID(t_id)查询学生
@Select("select * from student where t_id = #{t_id}")
public List<Student> findByUid(int uid); // uid对应教师表的id
}
4.3 延迟加载测试与原理分析
编写测试方法findAllLazy(),验证延迟加载效果:
java
@Test
public void findAllLazy(){
List<Teacher> list = mapper.findAllLazy(); // 执行查询教师的SQL,此时不查询学生数据
for (Teacher teacher: list) {
System.out.println(teacher.toString()); // 当访问teacher的students属性时,才执行StudentDao.findByUid()查询学生数据
}
}
延迟加载核心原理
MyBatis通过动态代理机制实现延迟加载:当执行findAllLazy()方法时,仅查询teacher表数据,返回的Teacher对象是代理对象;当程序首次访问Teacher对象的students属性时,代理对象会触发StudentDao.findByUid()方法,查询关联的学生数据并封装到students字段中。延迟加载可有效减少不必要的数据库查询,提升系统性能(尤其适用于关联数据量大的场景)。
@One与@Many的区别与适用场景
-
@One:用于一对一或多对一关联(如学生与班级:多个学生对应一个班级),返回单个关联对象。示例:@One(select = "com.qcby.dao.ClassDao.findById", fetchType = FetchType.LAZY)
-
@Many:用于一对多关联(如教师与学生),返回关联对象的集合。两者的核心区别在于返回值类型:@One对应单个对象,@Many对应集合。
五、MyBatis注解开发的优势与局限性
5.1 优势
-
开发高效:无需编写XML映射文件,直接在Mapper接口上通过注解编写SQL,简化开发流程。
-
代码简洁:SQL与Java代码在同一文件中,便于查看和维护(适用于简单SQL场景)。
-
支持核心功能:覆盖CRUD、结果映射、关联查询、延迟加载等核心场景,满足大部分开发需求。
5.2 局限性
-
不适用于复杂SQL:对于多表联查、动态SQL(if/where/foreach等),注解编写繁琐且可读性差,推荐使用XML方式。
-
SQL复用性差:注解中的SQL无法像XML那样通过<sql>标签复用,复杂场景下会导致代码冗余。
-
调试不便:注解中的SQL无法直接在XML中格式化,调试时需额外打印SQL语句。
六、总结与最佳实践建议
MyBatis注解开发是XML开发的有效补充,适用于简单CRUD场景和中小型项目;对于复杂SQL场景(如动态SQL、多表复杂联查),建议优先使用XML方式。结合本文案例,给出以下最佳实践建议:
-
简单CRUD用注解:查询、新增、修改、删除等简单SQL,直接使用@Select、@Insert等注解,提升开发效率。
-
结果集复用:通过@Results定义通用结果集,使用@ResultMap复用,减少重复代码。
-
关联查询合理选择加载方式:关联数据量大时,使用FetchType.LAZY实现延迟加载;数据量小时,可使用FetchType.EAGER立即加载。
-
复杂SQL用XML:动态SQL、多表复杂联查等场景,采用XML映射文件,提升SQL可读性和复用性。
-
防注入优先#{}:所有参数传递优先使用#{}占位符,避免${}导致的SQL注入风险。
通过本文的核心注解解析和实战案例,相信大家已掌握MyBatis注解开发的核心用法。在实际开发中,需根据项目场景灵活选择注解或XML方式,兼顾开发效率和系统可维护性。