Skip to content

MyBatis:Java 与数据库的桥梁

难度:中级 | 预计时间:50 分钟 | 前置:RESTful API


📖 JDBC 写吐了

故事背景

你刚用 Spring Boot 写完第一个 REST 接口,现在要把数据存到数据库。你查了一下 JDBC 的写法——打开连接、写 SQL 字符串、设参数、执行、遍历 ResultSet、手动映射到对象、关连接、处理异常……一个简单的"查所有用户"写了 40 行代码,其中 30 行是样板代码。

然后你发现了 MyBatis——写一个接口,加一个注解或 XML,声明 SQL。它会自动帮你:打开连接、设参数、执行 SQL、把结果映射成你的 Java 对象、关连接。样板代码消失了。作为前端,你可以把它类比成——JDBC 是原生 XMLHttpRequest,MyBatis 是 fetchaxios

💻 代码演示

对比 JDBC 原始写法 vs MyBatis 注解写法——同样的查询,代码量差多少:

java
// ===== JDBC 原始写法(40行样板)=====
public User findById(Long id) {
    String sql = "SELECT id, name, email FROM users WHERE id = ?";
    try (Connection conn = dataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setLong(1, id);
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            return new User(rs.getLong("id"),
                rs.getString("name"), rs.getString("email"));
        }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return null;
}

// ===== MyBatis 写法(1行SQL + 1个注解)=====
@Mapper
public interface UserMapper {
    @Select("SELECT id, name, email FROM users WHERE id = #{id}")
    User findById(Long id);

    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(User user);

    @Select("SELECT * FROM users WHERE name LIKE CONCAT('%',#{keyword},'%')")
    List<User> search(@Param("keyword") String keyword);

    @Update("UPDATE users SET name=#{name}, email=#{email} WHERE id=#{id}")
    void update(User user);

    @Delete("DELETE FROM users WHERE id=#{id}")
    void delete(Long id);
}

运行输出:

MyBatis 自动做了:连接管理、参数绑定(#{id}→?)、结果映射(列名→字段名驼峰转换)、资源关闭。你只需要写 SQL 和接口方法名。

🎯 三个核心问题

这是什么?

MyBatis 是一个半自动 ORM(Object-Relational Mapping)框架。不像 Hibernate/JPA 那样完全自动生成 SQL——MyBatis 要求你自己写 SQL,它只帮你做参数映射和结果映射。核心组件:Mapper 接口(定义方法)、SQL 映射(注解或 XML)、SqlSession(执行引擎)。Spring Boot 集成通过 mybatis-spring-boot-starter 自动配置。

为什么需要它?

为什么选 MyBatis 而不是 JPA/Hibernate?①SQL 可控——复杂查询你写什么就跑什么,没有 N+1 问题的惊喜 ②学习曲线低——不需要理解实体状态、持久化上下文、懒加载等概念 ③适合 SQL 密集型项目——报表、数据迁移等场景。JPA 适合简单 CRUD 为主的场景,MyBatis 适合你需要精确控制 SQL 的场景。

如果没有它会怎样?

如果只有 JDBC——样板代码淹没业务逻辑,字段名和列名的映射全靠手动,每个人写的查询风格不一致。如果只用 JPA——复杂查询难以优化,SQL 由框架生成不可控,调试时不知道实际执行的 SQL 长什么样。MyBatis 在灵活性和便利性之间取了一个很好的平衡。

📝 原理讲解

MyBatis 核心用法速查:

  • @Mapper:标记接口为 MyBatis Mapper。Spring Boot 会自动扫描并生成代理实现类。

  • #{参数名}预编译占位符——安全,防止 SQL 注入。MyBatis 用 PreparedStatement 设参。

  • ${参数名}直接字符串拼接——危险!仅用于动态表名/列名等无法预编译的场景。

  • @Options:配置自增主键回填(useGeneratedKeys=true, keyProperty="id")——插入后 user.getId() 就有值了。

  • @Param:多参数时命名。单个参数不需要(MyBatis 直接用参数名),但多参数时必须加。

  • XML 映射 vs 注解:简单 CRUD 用注解,复杂查询(动态 SQL、多表关联)用 XML。<where><if><foreach> 是动态 SQL 的核心标签。

  • 驼峰转换:配置 mybatis.configuration.map-underscore-to-camel-case=true,数据库列 created_at 自动映射到 Java 字段 createdAt

🎨 生活类比

类比理解

JDBC 是自己去菜市场买菜、洗菜、切菜、炒菜——每个步骤都要自己做(打开连接、设参数、执行、映射、关闭)。 MyBatis 是你写好菜单(SQL),交给采购专员——专员帮你买菜(参数绑定)、按菜谱做(执行 SQL)、摆盘(结果映射)、洗碗(关闭连接)。你只需要写好菜单。 #{ } 是密封包装——参数被严密包裹在 PreparedStatement 里,坏人(SQL 注入)无法篡改。${ } 是把食材直接扔进锅里——坏人可以在你的食材里夹带刀片('; DROP TABLE users; --')。

✏️ 动手练习

练习 1

在 Spring Boot 项目中加 H2 数据库和 MyBatis 依赖,配置 application.properties 连接 H2,写一个 UserMapper 实现 CRUD。

<details> <summary>💡 查看提示</summary>

依赖:spring-boot-starter-web、mybatis-spring-boot-starter、h2(runtime)。H2 连接:spring.datasource.url=jdbc:h2:mem:testdb。在 src/main/resources/schema.sql 里写建表语句。

</details>

练习 2

写一个搜索接口:GET /api/users/search?keyword=张,用 MyBatis 的 LIKE 查询模糊匹配用户名。注意用 #{} 而不是 ${}!

<details> <summary>💡 查看提示</summary>

@Select("SELECT * FROM users WHERE name LIKE CONCAT('%',#{keyword},'%')") —— 用 CONCAT 拼接而不用 ${},防止 SQL 注入。

</details>

练习 3

用 XML 映射写一个动态 SQL:根据可选条件(name 非空时筛选、email 非空时筛选)查询用户列表。

<details> <summary>💡 查看提示</summary>

src/main/resources/mapper/UserMapper.xml 里用 <select> 标签,内部用 <where> + <if test="name != null"> 实现动态条件。

</details>

✅ 自检站

<details> <summary><strong>#{} 和 ${} 的区别是什么?为什么绝大多数情况应该用 #{}?</strong></summary>

#{} 是预编译占位符——MyBatis 用 PreparedStatement.setXxx() 设参,安全且高效(SQL 可以缓存执行计划)。${} 是字符串直接替换——MyBatis 在编译 SQL 之前就把参数值拼进去了。${} 有 SQL 注入风险——如果参数是 "1; DROP TABLE users;--",整个表就没了。${} 仅在需要动态指定表名、列名、排序字段等无法预编译的地方使用,且必须对参数做白名单校验。

</details>

<details> <summary><strong>MyBatis 和 JPA/Hibernate 的核心区别是什么?各适合什么场景?</strong></summary>

MyBatis 是半自动——你写 SQL,它做映射。JPA/Hibernate 是全自动——你操作对象,它生成 SQL。MyBatis 适合:复杂查询多、需要 SQL 优化、报表统计、遗留数据库。JPA 适合:简单 CRUD 为主、快速开发、对象模型清晰的标准项目。国内企业偏向 MyBatis(SQL 可控),国外偏向 JPA(开发效率高)。

</details>

<details> <summary><strong>MyBatis 如何实现数据库字段名(下划线)到 Java 字段名(驼峰)的自动映射?</strong></summary>

在 application.properties 中配置 mybatis.configuration.map-underscore-to-camel-case=true。MyBatis 会自动将数据库的 created_at 映射到 Java 的 createdAtuser_name 映射到 userName。如果列名和字段名完全不同,用 @Results 注解或 XML <resultMap> 手动指定映射关系。

</details>