MyBatis注解开发全解析:从基础CRUD到关联查询与延迟加载

在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"/&gt;
    
    <!-- 方式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方式。结合本文案例,给出以下最佳实践建议:

  1. 简单CRUD用注解:查询、新增、修改、删除等简单SQL,直接使用@Select、@Insert等注解,提升开发效率。

  2. 结果集复用:通过@Results定义通用结果集,使用@ResultMap复用,减少重复代码。

  3. 关联查询合理选择加载方式:关联数据量大时,使用FetchType.LAZY实现延迟加载;数据量小时,可使用FetchType.EAGER立即加载。

  4. 复杂SQL用XML:动态SQL、多表复杂联查等场景,采用XML映射文件,提升SQL可读性和复用性。

  5. 防注入优先#{}:所有参数传递优先使用#{}占位符,避免${}导致的SQL注入风险。

通过本文的核心注解解析和实战案例,相信大家已掌握MyBatis注解开发的核心用法。在实际开发中,需根据项目场景灵活选择注解或XML方式,兼顾开发效率和系统可维护性。

相关推荐
程序员侠客行3 小时前
Mybatis入门到精通 一
java·架构·mybatis
又是忙碌的一天5 小时前
Myvatis 动态查询及关联查询
java·数据库·mybatis
柒.梧.5 小时前
深度解析MyBatis缓存机制:从基础原理到实战配置
缓存·mybatis
一直都在5726 小时前
MyBatis缓存
缓存·mybatis
7澄16 小时前
MyBatis缓存详解:一级缓存、二级缓存与实战优化
缓存·mybatis·一级缓存
胡闹548 小时前
MyBatis-Plus 更新字段为 null 为何失效?
java·数据库·mybatis
侠客行03178 小时前
Mybatis入门到精通 二
java·mybatis·源码阅读
侠客行031719 小时前
Mybatis入门到精通 一
java·mybatis·源码阅读
赵得C1 天前
完整 Oracle 12c 分页 Demo(Spring Boot+MyBatis+PageHelper)
spring boot·oracle·mybatis