Mybaits的注解开发是现代Java项目(特别是Spring Boot项目)中非常主流的开发方式。它能让你摆脱繁琐的XML文件,以一种更“Java-Native”的方式编写数据访问层,代码更简洁,开发效率更高。


Mybatis注解开发深度解析与实战

引子:为什么需要注解开发?XML不香了吗?

XML开发方式非常强大和灵活,特别是对于复杂的动态SQL。但它也有一些缺点:

  • 文件繁多:每个Mapper都需要一个.java接口文件和一个.xml映射文件,管理起来比较分散。
  • 跳转不便:在IDE中,从Java方法跳转到对应的XML SQL语句,有时不如直接看代码方便。
  • 对于简单SQL,显得“重”:一个简单的SELECT * FROM user WHERE id = ?,也需要配置一整个XML文件。

注解开发正是为了解决这些问题而生。它的核心思想是:

将SQL语句直接写在Mapper接口的方法上,用注解来代替XML标签的功能。

这样做的好处是:

  • 代码聚合:SQL和它对应的Java方法紧密地写在一起,一目了然。
  • 文件精简:不再需要独立的.xml文件,一个.java文件搞定一切。
  • 开发高效:对于中小型项目或简单的CRUD操作,注解开发速度非常快。

当然,这是一种选择,而不是替代。在企业开发中,常常是两者结合使用:简单的、固定的SQL用注解;复杂的、需要动态拼接的SQL用XML。


第一部分:注解开发的核心注解与基础实践

1. 核心CRUD注解

Mybatis提供了一套与SQL操作对应的核心注解:

  • @Select: 用于执行查询操作 (SELECT)。
  • @Insert: 用于执行插入操作 (INSERT)。
  • @Update: 用于执行更新操作 (UPDATE)。
  • @Delete: 用于执行删除操作 (DELETE)。

这些注解的值(value)就是一个字符串数组,里面直接填写你的SQL语句。

2. 动手实践:从XML到注解的改造

我们将以之前的UserMapper为例,一步步将其从XML方式改造为注解方式。

项目结构准备

我们将创建一个新的Mapper接口UserAnnotationMapper.java,以示区分,但项目结构保持不变。

mybatis-cache-demo/
├── ...
└── src/
└── main/
├── java/
│ └── com/
│ └── example/
│ ├── ...
│ ├── mapper/
│ │ ├── UserMapper.java // (保留XML版本,用于对比)
│ │ └── UserAnnotationMapper.java // 【新增】我们的注解版Mapper
│ └── test/
│ └── AnnotationTest.java // 【新增】我们的注解测试类
└── resources/
├── mappers/
│ └── UserMapper.xml // (保留)
└── mybatis-config.xml

改造步骤:

  1. 创建UserAnnotationMapper.java接口

    这个接口里,我们将用注解来定义之前在UserMapper.xml中写的SQL。

    // src/main/java/com/example/mapper/UserAnnotationMapper.java
    package com.example.mapper; import com.example.entity.User;
    import org.apache.ibatis.annotations.*; public interface UserAnnotationMapper { /**
    * 1. 查询操作
    * @Select 注解,将SQL语句直接写在注解的值里
    * Mybatis会自动将方法的参数(id)与SQL中的#{id}进行绑定
    */
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(Integer id); /**
    * 2. 插入操作
    * @Insert 注解
    * @Options 注解可以配置一些额外选项,比如获取自增主键
    * - useGeneratedKeys = true: 表示要获取数据库生成的键(通常是自增ID)
    * - keyProperty = "id": 将获取到的键值,设置到传入的参数对象(user)的id属性上
    */
    @Insert("INSERT INTO user(username, password) VALUES(#{username}, #{password})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertUser(User user); /**
    * 3. 更新操作
    * @Update 注解
    * 对于多个参数,推荐使用 @Param 注解为每个参数命名,SQL中通过名字引用
    */
    @Update("UPDATE user SET username = #{newUsername} WHERE id = #{id}")
    int updateUsername(@Param("id") Integer id, @Param("newUsername") String newUsername); /**
    * 4. 删除操作
    * @Delete 注解
    */
    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteById(Integer id);
    }
  2. mybatis-config.xml中注册新的Mapper接口

    对于注解开发的Mapper,我们不再使用<mapper resource="...">来指向XML文件,而是使用<mapper class="...">来直接指向Java接口。

    <!-- src/main/resources/mybatis-config.xml -->
    <mappers>
    <!-- 保留XML方式的注册 -->
    <mapper resource="mappers/UserMapper.xml"/> <!-- 【新增】注解方式的注册 -->
    <mapper class="com.example.mapper.UserAnnotationMapper"/>
    </mappers>

    Mybatis会自动识别接口上的注解,并为它们生成动态代理,无需XML文件。

  3. 创建AnnotationTest.java进行测试

    // src/main/java/com/example/test/AnnotationTest.java
    package com.example.test; import com.example.entity.User;
    import com.example.mapper.UserAnnotationMapper;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import java.io.IOException;
    import java.io.InputStream; public class AnnotationTest {
    public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 使用try-with-resources, autoCommit设置为false,方便手动控制事务
    try (SqlSession session = sqlSessionFactory.openSession(false)) {
    UserAnnotationMapper mapper = session.getMapper(UserAnnotationMapper.class); // --- 测试查询 ---
    System.out.println("--- Testing @Select ---");
    User foundUser = mapper.findById(1);
    System.out.println("Found User: " + foundUser); // --- 测试插入 ---
    System.out.println("\n--- Testing @Insert ---");
    User newUser = new User();
    newUser.setUsername("annotationUser");
    newUser.setPassword("anno123");
    System.out.println("Before insert, newUser id: " + newUser.getId());
    mapper.insertUser(newUser);
    System.out.println("After insert, newUser id (auto-generated): " + newUser.getId()); // --- 测试更新 ---
    System.out.println("\n--- Testing @Update ---");
    int updatedRows = mapper.updateUsername(newUser.getId(), "updatedAnnotationUser");
    System.out.println("Updated rows: " + updatedRows);
    User updatedUser = mapper.findById(newUser.getId());
    System.out.println("After update: " + updatedUser); // --- 测试删除 ---
    System.out.println("\n--- Testing @Delete ---");
    int deletedRows = mapper.deleteById(newUser.getId());
    System.out.println("Deleted rows: " + deletedRows); // 因为我们openSession时是手动提交,所以可以选择提交或回滚
    // 如果想让数据库真正发生改变,就取消这行注释
    session.commit();
    // 如果只是测试,不想污染数据库,就用回滚
    // session.rollback();
    }
    }
    }

3. 运行与分析

运行AnnotationTest.java,你会看到所有CRUD操作都通过注解成功执行了。特别注意@Insert的测试中,通过@Options注解,我们成功获取了数据库自增的ID并设置回了newUser对象中。


第二部分:进阶注解与企业级实践

简单的CRUD用注解很爽,但如果遇到列名和属性名不匹配、一对多/多对一关联查询等复杂情况,注解还能胜任吗?答案是可以的,Mybatis提供了一套更强大的注解来处理这些场景。

1. @Results@Result:解决列名与属性名不匹配

这套注解完全等同于XML中的<resultMap>

  • @Results: 相当于<resultMap>标签,它是一个容器,可以包含多个@Result。它有一个id属性,可以被其他查询引用。
  • @Result: 相当于<result><id>标签,用于定义单个列到属性的映射。
    • column: 数据库的列名。
    • property: Java对象的属性名。
    • id = true: 表明这是主键,相当于<id>标签。

企业级例子:数据库列名带下划线,Java属性是驼峰命名

假设我们的user表,列名是user_nameuser_pass,而User实体类的属性是usernamepassword

// 在UserAnnotationMapper.java中新增方法
public interface UserAnnotationMapper {
// ... 其他方法 ... /**
* 使用 @Results 和 @Result 手动映射列名和属性名
*/
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "user_name"),
@Result(property = "password", column = "user_pass")
})
@Select("SELECT id, user_name, user_pass FROM user WHERE id = #{id}")
User findByIdWithResultMap(Integer id); /**
* 使用 @ResultMap 注解复用上面定义好的映射关系
*/
@ResultMap("userResultMap") // 通过id引用上面定义好的@Results
@Select("SELECT id, user_name, user_pass FROM user")
List<User> findAll();
}

注意:虽然可以手动映射,但在企业开发中,更推荐的做法是在mybatis-config.xml中开启驼峰命名自动映射,这样就不需要为每个查询都写@Results了。

<setting name="mapUnderscoreToCamelCase" value="true"/>

2. @One@Many:处理关联查询

这套注解完全等同于XML中的<association><collection>,用于处理一对一和一对多关系。

企业级例子:查询用户及其所有订单(一对多)

  1. 准备新的实体类Order.java和修改User.java

    @Data
    public class Order implements Serializable {
    private Integer orderId;
    private String orderName;
    private Integer userId; // 外键
    } @Data
    public class User implements Serializable {
    private Integer id;
    private String username;
    // ...
    private List<Order> orders; // 一个用户有多个订单
    }
  2. UserAnnotationMapper中定义关联查询

    public interface UserAnnotationMapper {
    // ... @Select("SELECT * FROM `order` WHERE user_id = #{userId}")
    List<Order> findOrdersByUserId(Integer userId); @Select("SELECT * FROM user WHERE id = #{id}")
    @Results({
    @Result(property = "id", column = "id", id = true),
    @Result(property = "username", column = "username"),
    // @Many注解处理一对多关系
    // - property = "orders": 对应User类中的orders属性
    // - select = "...findOrdersByUserId": 指定一个查询方法,它会根据主查询的结果(这里的id)作为参数去执行
    // - column = "id": 将主查询的id列作为参数,传递给上面的select方法
    @Result(
    property = "orders",
    javaType = List.class,
    column = "id",
    many = @Many(select = "com.example.mapper.UserAnnotationMapper.findOrdersByUserId")
    )
    })
    User findUserWithOrders(Integer id);
    }

    工作流程:当调用findUserWithOrders(1)时,Mybatis会:

    1. 执行主查询SELECT * FROM user WHERE id = 1
    2. 拿到结果中的id值(=1)。
    3. 将这个id值作为参数,去调用findOrdersByUserId(1)方法。
    4. findOrdersByUserId返回的订单列表,设置到User对象的orders属性中。
    5. 最后返回完整的User对象。

3. @SelectProvider: 注解方式的动态SQL

注解的value属性只能写固定的SQL字符串,如果想实现动态SQL怎么办?Mybatis提供了@SelectProvider(以及@InsertProvider等)注解。

你需要创建一个Provider类,在其中编写一个返回String(即SQL语句)的Java方法。这个方法可以接收参数,并在Java代码中用if-elseStringBuilder等逻辑来动态构建SQL。

例子:动态搜索用户

// 1. 创建一个Provider类
public class UserSqlProvider {
// 方法必须是public static,返回String
public String findUserByCondition(Map<String, Object> params) {
// 使用Mybatis内置的SQL构建器,比StringBuilder更优雅
return new SQL() {{
SELECT("*");
FROM("user");
if (params.get("username") != null) {
WHERE("username like #{username}");
}
if (params.get("email") != null) {
WHERE("email = #{email}");
}
}}.toString();
}
} // 2. 在Mapper接口中使用它
public interface UserAnnotationMapper {
// ...
@SelectProvider(type = UserSqlProvider.class, method = "findUserByCondition")
List<User> findUserByCondition(Map<String, Object> params);
}

思考:虽然Provider可以实现动态SQL,但其逻辑写在Java代码中,不如XML的动态SQL标签直观。因此,对于复杂的动态SQL,业界普遍认为XML是更好的选择。


总结:注解 VS XML,如何选择?

场景 推荐方式 理由
简单的CRUD 注解 快速、简洁、代码聚合
列名与属性名不匹配 注解 (@Results) 或 全局配置 注解灵活,全局配置一劳永逸
简单的关联查询 注解 (@One, @Many) 对于清晰的一对一、一对多关系,注解足够清晰
复杂的动态SQL XML XML标签的可读性和组合能力远超Java代码拼接
超长、复杂的SQL XML 避免在Java注解里写一个超长的、难以阅读的SQL字符串
SQL需要后期优化和维护 XML DBA或后端开发者可以不改Java代码,直接优化XML中的SQL

企业级开发的黄金法则:

使用注解处理简单、固定的SQL,让开发飞起来;使用XML处理复杂、动态的SQL,让维护和优化更从容。两者是互补的战友,而不是互斥的敌人。

现在,你可以开始动手把之前的例子用注解重写一遍,并尝试一下关联查询和动态SQL Provider,亲身体会这两种开发模式的异同和优劣。

Mybatis - 精巧的持久层框架 - 注解开发深刻理解的更多相关文章

  1. MyBatis(四):自定义持久层框架优化

    本文所有代码已上传至码云:https://gitee.com/rangers-sun/mybatis 修改IUserDao.UserMapper.xml package com.rangers; im ...

  2. MyBatis(三):自定义持久层框架实现

    代码已上传至码云:https://gitee.com/rangers-sun/mybatis 新建Maven工程 架构端MyPersistent.使用端MyPersistentTest,使用端引入架构 ...

  3. MyBatis(二):自定义持久层框架思路分析

    使用端 引入架构端Maven依赖 SqlMapConfig.xml-数据库配置信息(数据库连接jar名称.连接URL.用户名.密码),引入Mapper.xml的路径 XxMapper.xml-SQL配 ...

  4. Mybatis详解系列(一)--持久层框架解决了什么及如何使用Mybatis

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  5. 开源顶级持久层框架——mybatis(ibatis)——day02

    mybatis第二天    高级映射 查询缓存 和spring整合          课程复习:         mybatis是什么?         mybatis是一个持久层框架,mybatis ...

  6. Mybatis学习之自定义持久层框架(二) 自定义持久层框架设计思路

    前言 上一篇文章讲到了JDBC的基本用法及其问题所在,并提出了使用Mybatis的好处,那么今天这篇文章就来说一下该如何设计一个类似Mybatis这样的持久层框架(暂时只讲思路,具体的代码编写工作从下 ...

  7. spring-boot+mybatis开发实战:如何在spring-boot中使用myabtis持久层框架

    前言: 本项目基于maven构建,使用mybatis-spring-boot作为spring-boot项目的持久层框架 spring-boot中使用mybatis持久层框架与原spring项目使用方式 ...

  8. 持久层框架之MyBatis

    1.mybatis框架介绍: MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并 ...

  9. Java数据持久层框架 MyBatis之背景知识三

    摘录自:http://www.cnblogs.com/lcngu/p/5437281.html 对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.or ...

  10. Java数据持久层框架 MyBatis之背景知识二

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

随机推荐

  1. Redis 过期键删除和内存淘汰策略【Redis 系列之四】

    〇.前言 对于 Redis 服务器来说,内存资源非常宝贵,如果一些过期键一直不被删除,就会造成资源浪费. 那么,本文将结合博主收集的资料,简单介绍下过期键删除.内存淘汰两个策略,仅供参考. 博主 Re ...

  2. 抽象类的注意事项、abstract关键字的冲突--java进阶day02

    1.注意事项 1.抽象类不允许创建对象 2.抽象类存在构造方法 3.抽象类中可以存在普通成员方法 4.抽象类的子类存在两种处理方式 第一种不多解释,主要讲第二种,子类继承了抽象类,相当于子类里面有了抽 ...

  3. C#元数据的概念,以及一个使用了lambda表达式的简单例子

    先看一个例子 假设你写了一个 C# 类库 MathUtils.dll: public class Calculator { public int Add(int a, int b) => a + ...

  4. WebKit Inside: 渲染树

    经过CSS的匹配,就要进入渲染树的构建. 渲染树也叫RenderObject树,因为渲染树上每一个节点,都是RenderObject的子类. 首先来看一下RenderObject的继承类图. 1 Re ...

  5. rabbitmq的消息的有顺序性

    一.rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点:或者就一个queue但是对应一个consumer,然后这个consumer内部用内 ...

  6. Avalnoia跨平台实战记录(一),Avalonia初始化

    前言: 记录一下小菜鸟程序员从WPF一知半解转向Avalonia跨平台桌面端开发的一点记录和感想,我个人是比较喜欢用.NET来开发的,当然,这也和我的技术栈有很大关系,本人只是从大专出来的,在学校里学 ...

  7. python获取指定文件夹内文件名称

    比如下图,文件夹内有若干文件,且文件夹路径:C:\Users\Administrator\Desktop\2147\1024 4行代码,解决问题 import os path = "C:\\ ...

  8. Java编程之容器类

    一.ArrayList 1.创建ArrayList对象 ArrayList<String> arr=new ArrayList<>(); //添加<>的为泛型 // ...

  9. 2025dsfz集训Day9:树状数组、LCA、RMQ

    Day8 I:树状数组 \[Designed\ By\ FrankWkd\ -\ Luogu@Lwj54joy,uid=845400 \] \[特别感谢 此次课的主讲.图源侵删 \] 后记:关于本文的 ...

  10. jdbc写一个访问数据库的工具类

    操作的工具类 package com.zjw.jdbc2; /** * jdbc操作的工具类 * @author Administrator * */ import java.sql.Connecti ...