乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

Mapper接口与映射方式

导学

通过本节学习,你将能够:

  • 掌握 MyBatis 的两种映射方式:XML 映射文件与注解映射
  • 理解 XML 映射文件的完整结构(DOCTYPE、namespace、CRUD 标签)
  • 熟练使用 @Select、@Insert、@Update、@Delete 完成注解映射
  • 根据项目场景正确选择 XML 或注解方式,避免混合使用带来的维护灾难

定义

什么是映射方式

映射方式指 SQL 语句与 Java Mapper 接口方法之间的绑定机制。MyBatis 提供两种绑定途径:

方式SQL 存放位置绑定机制
XML 映射文件src/main/resources/mapper/*.xml通过 namespace + id 与接口方法匹配
注解映射Mapper 接口的方法上通过 @Select/@Insert/@Update/@Delete 直接声明 SQL

解决了什么痛点

在 JDBC 时代,SQL 与 Java 代码深度耦合:

// JDBC 的噩梦:SQL 字符串散落在业务代码中
String sql = "SELECT * FROM student WHERE major = '" + major + "'";
// 修改 SQL 需要改 Java 代码 → 重新编译 → 重新部署

MyBatis 的两种映射方式都实现了 SQL 与 Java 代码的解耦,但解耦程度和适用场景不同:

  • XML 方式:SQL 完全独立于 Java 文件,DBA 可直接审阅,适合复杂 SQL、动态 SQL
  • 注解方式:SQL 与接口同处一个文件,开发时无需切换窗口,适合简单 CRUD

核心原理

XML 方式 vs 注解方式对比流程图

流程解读:

  • XML 方式:接口与 SQL 分离,MyBatis 启动时解析 XML 文件,将 namespace.id 与 SQL 语句注册到 Configuration 中
  • 注解方式:接口与 SQL 合一,MyBatis 启动时扫描接口上的注解,同样将 接口全限定名.方法名 与 SQL 注册到 Configuration 中
  • 殊途同归:无论哪种方式,最终都生成相同的 MappedStatement,后续由 Executor 执行时无差异

完整示例

场景说明

乐途公司学生管理系统的 StudentMapper 需要支持查询所有学生、按 ID 查询、新增、更新、删除五个操作。本节分别用 XML 方式和注解方式实现同一套功能,便于直观对比。

操作前的数据库表结构及初始数据

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

公共部分(两种方式共用)

实体类 Student.java

package com.fly.entity;

import java.math.BigDecimal;

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String major;
    private BigDecimal score;

    public Student() {}
    public Student(String name, Integer age, String major, BigDecimal score) {
        this.name = name; this.age = age; this.major = major; this.score = score;
    }
    // Getter / Setter 省略...
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getMajor() { return major; }
    public void setMajor(String major) { this.major = major; }
    public BigDecimal getScore() { return score; }
    public void setScore(BigDecimal score) { this.score = score; }
    @Override
    public String toString() {
        return "Student{id=" + id + ", name='" + name + "', age=" + age 
            + ", major='" + major + "', score=" + score + "}";
    }
}

工具类 MyBatisUtil.java

package com.fly.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;

public class MyBatisUtil {
    private static final SqlSessionFactory factory;
    static {
        try (InputStream is = Resources.getResourceAsStream("mybatis-config.xml")) {
            factory = new SqlSessionFactoryBuilder().build(is);
        } catch (Exception e) { throw new ExceptionInInitializerError(e); }
    }
    public static SqlSessionFactory getFactory() { return factory; }
}

方式一:XML 映射文件

Mapper 接口 StudentMapper.java

package com.fly.mapper;

import com.fly.entity.Student;
import java.util.List;

public interface StudentMapper {
    Student findById(Integer id);
    List<Student> findAll();
    int insert(Student student);
    int update(Student student);
    int deleteById(Integer id);
}

映射文件 StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace 必须与接口全限定名一致 -->
<mapper namespace="com.fly.mapper.StudentMapper">

    <select id="findById" resultType="com.fly.entity.Student">
        SELECT id, name, age, major, score 
        FROM student 
        WHERE id = #{id}
    </select>

    <select id="findAll" resultType="com.fly.entity.Student">
        SELECT id, name, age, major, score FROM student
    </select>

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO student (name, age, major, score)
        VALUES (#{name}, #{age}, #{major}, #{score})
    </insert>

    <update id="update">
        UPDATE student 
        SET name = #{name}, age = #{age}, major = #{major}, score = #{score}
        WHERE id = #{id}
    </update>

    <delete id="deleteById">
        DELETE FROM student WHERE id = #{id}
    </delete>

</mapper>

全局配置中注册方式

<mappers>
    <!-- XML 方式:按资源路径注册 -->
    <mapper resource="mapper/StudentMapper.xml"/>
</mappers>

方式二:注解映射

Mapper 接口 StudentMapper.java(SQL 直接写在注解中)

package com.fly.mapper;

import com.fly.entity.Student;
import org.apache.ibatis.annotations.*;
import java.util.List;

public interface StudentMapper {

    @Select("SELECT id, name, age, major, score FROM student WHERE id = #{id}")
    @Results(id = "studentMap", value = {
        @Result(property = "id", column = "id"),
        @Result(property = "name", column = "name"),
        @Result(property = "age", column = "age"),
        @Result(property = "major", column = "major"),
        @Result(property = "score", column = "score")
    })
    Student findById(Integer id);

    @Select("SELECT id, name, age, major, score FROM student")
    @ResultMap("studentMap")
    List<Student> findAll();

    @Insert("INSERT INTO student (name, age, major, score) VALUES (#{name}, #{age}, #{major}, #{score})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Student student);

    @Update("UPDATE student SET name = #{name}, age = #{age}, major = #{major}, score = #{score} WHERE id = #{id}")
    int update(Student student);

    @Delete("DELETE FROM student WHERE id = #{id}")
    int deleteById(Integer id);
}

全局配置中注册方式

<mappers>
    <!-- 注解方式:按接口类注册 -->
    <mapper class="com.fly.mapper.StudentMapper"/>
</mappers>

测试代码与执行结果

测试入口(两种方式测试逻辑相同,仅 Mapper 注册方式不同)

package com.fly.test;

import com.fly.entity.Student;
import com.fly.mapper.StudentMapper;
import com.fly.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.math.BigDecimal;
import java.util.List;

public class MapperTest {
    public static void main(String[] args) {
        // 1. 查询所有
        try (SqlSession session = MyBatisUtil.getFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            List<Student> all = mapper.findAll();
            System.out.println("【查询全部】共 " + all.size() + " 条记录:");
            all.forEach(s -> System.out.println("  " + s));
        }

        // 2. 按 ID 查询
        try (SqlSession session = MyBatisUtil.getFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student s = mapper.findById(3);
            System.out.println("\n【按 ID 查询】id=3 => " + s);
        }

        // 3. 插入
        try (SqlSession session = MyBatisUtil.getFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student newbie = new Student("赵六", 23, "人工智能", new BigDecimal("91.00"));
            mapper.insert(newbie);
            session.commit();
            System.out.println("\n【插入】生成主键: " + newbie.getId());
        }

        // 4. 更新
        try (SqlSession session = MyBatisUtil.getFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student update = mapper.findById(1);
            update.setScore(new BigDecimal("99.00"));
            mapper.update(update);
            session.commit();
            System.out.println("\n【更新】大翔分数更新为: " + update.getScore());
        }

        // 5. 删除
        try (SqlSession session = MyBatisUtil.getFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            int rows = mapper.deleteById(6); // 删除赵六
            session.commit();
            System.out.println("\n【删除】影响行数: " + rows);
        }

        // 6. 验证最终状态
        try (SqlSession session = MyBatisUtil.getFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            List<Student> all = mapper.findAll();
            System.out.println("\n【最终数据】共 " + all.size() + " 条记录:");
            all.forEach(s -> System.out.println("  " + s));
        }
    }
}

实际执行结果(控制台输出)

【查询全部】共 5 条记录:
  Student{id=1, name='大翔', age=22, major='计算机科学', score=95.50}
  Student{id=2, name='白歌', age=21, major='软件工程', score=88.00}
  Student{id=3, name='小崔', age=20, major='计算机科学', score=92.00}
  Student{id=4, name='黄俪', age=21, major='信息安全', score=90.50}
  Student{id=5, name='李眉', age=22, major='软件工程', score=87.00}

【按 ID 查询】id=3 => Student{id=3, name='小崔', age=20, major='计算机科学', score=92.00}

【插入】生成主键: 6

【更新】大翔分数更新为: 99.00

【删除】影响行数: 1

【最终数据】共 5 条记录:
  Student{id=1, name='大翔', age=22, major='计算机科学', score=99.00}
  Student{id=2, name='白歌', age=21, major='软件工程', score=88.00}
  Student{id=3, name='小崔', age=20, major='计算机科学', score=92.00}
  Student{id=4, name='黄俪', age=21, major='信息安全', score=90.50}
  Student{id=5, name='李眉', age=22, major='软件工程', score=87.00}

最终数据状态:

idnameagemajorscore
1大翔22计算机科学99.00
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

XML vs 注解 综合对比

对比维度XML 映射文件注解映射
SQL 位置独立的 .xml 文件接口方法的注解中
动态 SQL强大,支持 <if>、<choose>、<foreach> 等标签较弱,需用 @SelectProvider 等注解,代码冗长
结果映射resultMap 标签功能完善,支持关联映射@Results + @Result 可实现,但复杂关联时可读性差
SQL 审阅DBA/运维可直接查看 XML,无需看 JavaSQL 散落在 Java 代码中,不利于独立审阅
开发效率需维护两个文件,切换成本高一个文件搞定,简单 CRUD 开发快
热部署修改 XML 后无需重新编译(部分容器支持热加载)修改注解需重新编译
适用场景复杂 SQL、动态 SQL、多表关联、需要 DBA 审阅简单 CRUD、快速原型、微服务轻量接口
注册方式<mapper resource="..."/><mapper class="..."/>

易错场景 / 常见误区

误区错误表现正解
同一接口混用 XML 和注解接口方法上写 @Select,同时存在同名 XML,导致行为不可预期一个 Mapper 接口只选一种方式,要么纯 XML,要么纯注解
注解方式忘记 @Results数据库列名与属性名不一致时,映射结果为 null使用 @Results 显式指定 column 与 property 映射关系,或用 @ResultMap 复用
XML 的 namespace 写错Invalid bound statement (not found)namespace 必须是接口的全限定名,含包名
注解的 @Insert 忘记 @Options插入后实体对象的 id 仍为 null需要 @Options(useGeneratedKeys = true, keyProperty = "id") 回填主键
注解方式用 <mapper resource="..."/> 注册启动不报错,但执行时 not found注解方式必须用 <mapper class="接口全限定名"/> 注册
XML 中 resultType 用别名但未配置ClassNotFoundException 或解析失败在 mybatis-config.xml 的 <typeAliases> 中注册包路径或类别名

面试考点

Q1:MyBatis 的 XML 映射方式和注解映射方式有什么区别?如何选择?

A: XML 方式将 SQL 写在独立的 XML 文件中,与 Java 代码完全解耦,支持强大的动态 SQL(<if>、<foreach> 等),适合复杂查询、需要 DBA 审阅 SQL、或 SQL 频繁变更的场景。注解方式将 SQL 直接写在接口方法的注解上(@Select、@Insert 等),开发效率高,一个文件即可完成,适合简单 CRUD、快速原型或微服务轻量接口。生产级项目推荐以 XML 为主,复杂逻辑用 XML,极简单点查询可酌情用注解,但同一 Mapper 不要混用两种方式。

Q2:为什么 Mapper 接口的 namespace 必须与接口全限定名一致?

A: MyBatis 通过 namespace + id 作为唯一键,从 Configuration 中定位 MappedStatement。当调用 session.getMapper(StudentMapper.class) 时,动态代理会根据接口全限定名和方法名拼接出 com.fly.mapper.StudentMapper.findById 去查找对应的 SQL。如果 namespace 不一致,就无法建立接口方法与 XML 中 SQL 的绑定关系,执行时会抛出 Invalid bound statement (not found)。

Q3:注解方式中 @ResultMap("studentMap") 是如何被识别和复用的?

A: 在 MyBatis 注解中,@Results 注解可以指定 id 属性(如 @Results(id = "studentMap", ...)),这相当于定义了一个具名的结果映射。同一接口的其他方法可以通过 @ResultMap("studentMap") 引用它,避免重复书写列与属性的映射关系。这个 id 只在当前 Mapper 接口的命名空间内有效。

Q4:如果项目中有 100 个 Mapper,如何简化 <mappers> 的注册配置?

A: 可以使用包扫描方式批量注册:<package name="com.fly.mapper"/>。使用此方式时,MyBatis 会自动扫描 com.fly.mapper 包下的所有接口。对于 XML 方式,要求 XML 文件与接口同名且同路径(如 com/fly/mapper/StudentMapper.xml 放在 resources 下);对于注解方式,直接扫描接口类即可。这比逐个写 <mapper resource="..."/> 简洁得多。


小结

本节详细对比了 MyBatis 的两种映射方式:XML 映射文件和注解映射。XML 方式以 "SQL 与代码解耦" 为核心优势,配合强大的动态 SQL 能力,是企业级项目的首选。注解方式以 "开发效率" 为核心优势,适合简单场景。无论哪种方式,namespace 与接口全限定名的一致性、正确的注册方式、以及避免混用,都是保证项目可维护性的关键。

关键记忆点:

  • XML 方式:<mapper resource="..."/>,SQL 独立,动态 SQL 强大
  • 注解方式:<mapper class="..."/>,@Select/@Insert/@Update/@Delete,开发快捷
  • 同一 Mapper 严禁混用两种方式
  • namespace = 接口全限定名,id = 接口方法名
  • 包扫描 <package name="..."/> 可大幅简化配置

下一章引子

至此,第 01 章 "MyBatis 概述与快速上手" 已全部完成。你已经掌握了 MyBatis 的定位、环境搭建、核心对象生命周期、第一个完整 CRUD 程序,以及两种映射方式的选择策略。第 02 章将深入 MyBatis 配置详解——从 properties、settings、typeAliases 到插件和缓存配置,带你把 mybatis-config.xml 的每一项都理解透彻,为复杂项目打下坚实基础。

上一页
不使用 XML 构建 SqlSessionFactory
下一页
Java API 目录结构