MyBatis

MyBatis 持久层基础

MyBatis 把 Mapper 接口方法、SQL 定义、参数绑定和结果映射组织起来,减少手写 JDBC 样板代码,但 SQL 设计和事务边界仍由开发者负责。

MyBatis 执行链

Spring Boot 快速接入

准备步骤:

  1. 引入 MyBatis Starter 与数据库驱动。
  2. 准备数据表和 Java 实体。
  3. 配置 DataSource
  4. 定义 Mapper 接口。
  5. 使用注解或 XML 编写 SQL。
代码块JAVA · 5 行收起展开
@Mapper
public interface UserMapper {
    @Select("select id, username, name, age from user")
    List<User> findAll();
}

测试类使用 @SpringBootTest 时会加载 Spring Boot 容器;测试包通常放在启动类所在包或其子包下。

MyBatis 与 JDBC

MyBatis 仍然使用 JDBC:

  • DataSource 获取连接。
  • 根据 Mapper 方法找到 SQL 定义。
  • 生成 PreparedStatement 并绑定参数。
  • 执行 SQL。
  • ResultSet 映射为 Java 对象。

它不会替你决定索引、连接方式、锁、事务隔离级别或 SQL 是否高效。

基本 CRUD

代码块JAVA · 18 行收起展开
@Delete("delete from user where id = #{id}")
int deleteById(Long id);

@Insert("""
    insert into user(username, password, name, age)
    values(#{username}, #{password}, #{name}, #{age})
    """)
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

@Update("""
    update user
    set username = #{username},
        name = #{name},
        age = #{age}
    where id = #{id}
    """)
int update(User user);

DML 方法建议返回受影响行数,用于判断目标是否存在以及乐观更新是否成功。

#{...}${...}

语法机制适用范围风险
代码块JAVA · 2 行收起展开
| `#{value}` | 预编译占位符 | 普通参数值 | 安全,默认使用 |
| `${value}` | 文本拼接 | 表名、列名等结构片段 | 有 SQL 注入风险 |

表名、排序列等结构不能由 ? 绑定,必须在服务端映射白名单:

代码块JAVA · 5 行收起展开
String orderBy = switch (sort) {
    case "name" -> "name";
    case "createdAt" -> "create_time";
    default -> "id";
};

不要直接把用户输入交给 ${sort}

多参数与 @Param

代码块JAVA · 9 行收起展开
@Select("""
    select id, username, name
    from user
    where username = #{username}
      and password = #{password}
    """)
User findByCredentials(
        @Param("username") String username,
        @Param("password") String password);

@Param 为参数建立稳定名称,避免依赖编译器是否保留形参名。真实系统不应明文保存或查询密码,应保存慢哈希并在应用层验证。

XML 映射

复杂 SQL 更适合 XML。默认约定:

  1. XML 与 Mapper 同名。
  2. namespace 是 Mapper 全限定名。
  3. SQL 的 id 与接口方法名一致。
  4. resultTyperesultMap 与返回结构匹配。
代码块XML · 6 行收起展开
<mapper namespace="com.example.mapper.UserMapper">
  <select id="findAll" resultType="com.example.pojo.User">
    select id, username, name, age
    from user
  </select>
</mapper>

结果映射

数据库 create_time 与 Java createTime 可通过以下方式对应:

  • SQL 别名:create_time as createTime
  • 开启下划线转驼峰:mybatis.configuration.map-underscore-to-camel-case: true
  • 使用 resultMap 显式映射。

简单平铺对象用 resultType;字段名不一致、嵌套对象、一对多集合等复杂结构用 resultMap

连接池

Spring Boot 默认通常使用 HikariCP,也可以显式切换 Druid。MyBatis 使用容器中的 DataSource,业务代码不应自行重复创建驱动连接。

常见失误

  • Mapper XML 的 namespace 或 id 与接口不一致。
  • 动态 SQL 末尾多出逗号、AND 或分号。
  • 查询列缺少别名,导致属性没有回填。
  • 在循环中逐条查询,形成 N+1。
  • 使用 ${} 拼接不可信输入。
  • 只依赖日志看到 SQL,却没有检查真实参数、执行计划和受影响行数。

延伸阅读