mappers
导学
本节学习目标:
- 理解
mappers是 MyBatis 全局配置与 SQL 映射文件的"纽带" - 掌握四种 Mapper 引入方式:
resource、url、class、package - 能够根据项目结构选择最合适的引入方式
- 了解 Mapper 注册与解析的完整流程,规避路径错误和命名空间不匹配等常见问题
定义
mybatis-config.xml 中配置好了数据源、事务、类型别名等全局设置后,MyBatis 还需要知道去哪里加载 SQL 映射定义。这些定义可能存在于:
- XML 文件(传统的
Mapper.xml) - Java 接口类(注解式 Mapper,如
@Select、@Insert) - 混合模式(接口 + XML)
mappers 元素的作用就是注册所有 Mapper 映射器,让 MyBatis 在初始化阶段解析并加载其中的 SQL 语句、结果映射、缓存配置等。
适用位置与核心属性
mappers 位于 mybatis-config.xml 的最末尾,是 configuration 的最后一个子元素。
<mappers>
<!-- 方式1:从类路径加载 XML -->
<mapper resource="mapper/EmployeeMapper.xml"/>
<!-- 方式2:从绝对 URL 加载 XML -->
<mapper url="file:///opt/config/mapper/DepartmentMapper.xml"/>
<!-- 方式3:注册接口类(注解 Mapper 或接口+XML 混合) -->
<mapper class="com.feixiang.mapper.SalaryMapper"/>
<!-- 方式4:包扫描(推荐) -->
<package name="com.feixiang.mapper"/>
</mappers>
四种引入方式对比
| 方式 | 属性 | 适用场景 | 注意事项 |
|---|---|---|---|
resource | 类路径下的相对路径 | 传统 XML Mapper,文件放在 resources 目录下 | 路径以 resources 为根,不加 / 开头也可 |
url | 绝对 URL | 配置文件放在服务器固定目录或远程地址 | 必须是绝对路径,如 file:/// 或 http:// |
class | 接口类的全限定名 | 纯注解 Mapper,或接口与 XML 同名同路径 | 若使用 XML,XML 必须与接口同名且放在接口所在包的类路径下 |
package | 包路径 | 批量注册,项目中最常用 | 扫描包下所有接口类,自动查找同名 XML |
核心原理
映射器注册与解析流程图
关键机制:
MapperRegistry是Configuration内部的映射器注册中心,维护Map<Class<?>, MapperProxyFactory<?>>- 每个 Mapper 接口会被动态代理封装为
MapperProxy,当调用接口方法时,代理对象从MappedStatements中查找对应的 SQL 定义 - 使用
class或package时,MyBatis 会尝试在接口所在包的类路径下查找同名 XML(如com/feixiang/mapper/EmployeeMapper.xml),实现"接口 + XML"的混合模式
完整示例
场景说明
乐途公司员工管理系统的 Mapper 文件组织如下:
EmployeeMapper:接口 + XML 混合模式DepartmentMapper:纯注解模式SalaryMapper:接口 + XML 混合模式
要求使用 package 扫描批量注册,同时演示 resource 方式的兜底配置。
操作前的状态
项目目录结构:
src/main/java/com/feixiang/mapper/
├── EmployeeMapper.java
├── EmployeeMapper.xml
├── DepartmentMapper.java
├── SalaryMapper.java
└── SalaryMapper.xml
EmployeeMapper.java:
package com.feixiang.mapper;
import com.feixiang.entity.Employee;
import org.apache.ibatis.annotations.Param;
public interface EmployeeMapper {
Employee selectById(@Param("id") Integer id);
int insert(Employee employee);
}
DepartmentMapper.java(纯注解):
package com.feixiang.mapper;
import com.feixiang.entity.Department;
import org.apache.ibatis.annotations.Select;
public interface DepartmentMapper {
@Select("SELECT department_code, department_name FROM department WHERE department_code = #{code}")
Department selectByCode(String code);
}
完整配置代码
步骤一:确保 XML 与接口同包同路径
EmployeeMapper.xml 放在 src/main/resources/com/feixiang/mapper/ 目录下(与接口包路径一致):
<?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">
<mapper namespace="com.feixiang.mapper.EmployeeMapper">
<select id="selectById" resultType="employee">
SELECT employee_id, employee_name, department_code, create_time
FROM employee
WHERE employee_id = #{id}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="employeeId">
INSERT INTO employee (employee_name, department_code)
VALUES (#{employeeName}, #{departmentCode})
</insert>
</mapper>
步骤二:配置 mappers
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.feixiang.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- Mapper 注册配置 -->
<mappers>
<!-- 方式一:resource 显式指定 XML(适合少量文件或文件路径与包路径不一致时) -->
<!-- <mapper resource="com/feixiang/mapper/EmployeeMapper.xml"/> -->
<!-- 方式二:url 从外部加载(适合配置文件外置) -->
<!-- <mapper url="file:///opt/mapper/EmployeeMapper.xml"/> -->
<!-- 方式三:class 注册单个接口(适合纯注解 Mapper) -->
<!-- <mapper class="com.feixiang.mapper.DepartmentMapper"/> -->
<!-- 方式四:package 扫描(强烈推荐,批量注册) -->
<package name="com.feixiang.mapper"/>
</mappers>
</configuration>
步骤三:Java 测试代码
public class MappersDemo {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
try (SqlSession session = factory.openSession()) {
// 测试 package 扫描注册的 EmployeeMapper(接口 + XML)
EmployeeMapper empMapper = session.getMapper(EmployeeMapper.class);
Employee emp = empMapper.selectById(1);
System.out.println("EmployeeMapper 查询: " + (emp != null ? emp.getEmployeeName() : "无数据"));
// 测试 package 扫描注册的 DepartmentMapper(纯注解)
DepartmentMapper deptMapper = session.getMapper(DepartmentMapper.class);
Department dept = deptMapper.selectByCode("DEV001");
System.out.println("DepartmentMapper 查询: " + (dept != null ? dept.getDepartmentName() : "无数据"));
}
}
}
实际效果/结果
控制台输出:
EmployeeMapper 查询: 王五
DepartmentMapper 查询: 研发部
启动日志(DEBUG 级别):
DEBUG o.a.i.b.MapperRegistry - Registering mapper class com.feixiang.mapper.EmployeeMapper
DEBUG o.a.i.b.MapperRegistry - Registering mapper class com.feixiang.mapper.DepartmentMapper
DEBUG o.a.i.b.MapperRegistry - Registering mapper class com.feixiang.mapper.SalaryMapper
效果验证:
package扫描自动注册了com.feixiang.mapper包下的所有接口EmployeeMapper和SalaryMapper的同名 XML 被自动发现并解析DepartmentMapper没有 XML,仅解析接口上的@Select注解- 所有 Mapper 均可通过
session.getMapper(XxxMapper.class)正常获取
分析
package扫描是实际项目中最推荐的方式,它自动处理接口与 XML 的关联,新增 Mapper 时无需修改mybatis-config.xml- 使用
class或package时,XML 文件必须与接口同名且位于接口所在包的类路径下。例如接口com.feixiang.mapper.EmployeeMapper对应的 XML 必须是com/feixiang/mapper/EmployeeMapper.xml resource方式的路径以resources目录为根,写com/feixiang/mapper/EmployeeMapper.xml或/com/feixiang/mapper/EmployeeMapper.xml均可(MyBatis 会自动处理前导/)- 同一个 Mapper 接口不应通过多种方式重复注册,否则启动时会报
Mapper interface xxx is already registered
易错场景/常见误区
| 误区 | 错误表现 | 正解 |
|---|---|---|
resource 路径写错 | 启动报 Could not find resource mapper/EmployeeMapper.xml | 路径以 resources 为根,检查文件是否编译到 target/classes 对应目录下 |
| 接口与 XML 不同名 | package 扫描后 XML 未加载,调用方法报 Invalid bound statement (not found) | 确保 XML 文件名与接口名完全一致(包括大小写),且包路径一致 |
XML 放在 java 目录下但未被编译 | Maven 项目下,.xml 文件未打包到 classes 目录 | 在 pom.xml 中配置资源过滤,或将 XML 移到 resources 目录下保持包路径 |
namespace 写错 | 启动不报错,但执行时报 Invalid bound statement | mapper 标签的 namespace 必须是接口的全限定名,如 com.feixiang.mapper.EmployeeMapper |
| 重复注册同一 Mapper | 启动报 Mapper interface com.feixiang.mapper.EmployeeMapper is already registered | 避免同时使用 package 扫描和 class/resource 显式注册同一个 Mapper |
纯注解 Mapper 使用 resource 指向 XML | 报 Could not find resource | 纯注解 Mapper 应使用 class 或 package 方式注册,没有 XML 文件 |
url 使用相对路径 | 找不到文件 | url 必须是绝对路径,如 file:///C:/config/mapper/EmployeeMapper.xml |
面试考点
Q1:MyBatis 注册 Mapper 有哪几种方式?推荐使用哪种?
A:四种方式:
resource(加载类路径 XML)、url(加载绝对 URL 的 XML)、class(注册接口类)、package(扫描包下所有接口)。推荐使用package扫描,因为它能批量注册,自动关联接口与同名 XML,新增 Mapper 时无需修改全局配置文件,维护成本最低。
Q2:使用 package 扫描时,MyBatis 如何找到接口对应的 XML 文件?
A:MyBatis 会根据接口的全限定名,将
.替换为/,并在类路径下查找同名 XML。例如接口com.feixiang.mapper.EmployeeMapper会查找com/feixiang/mapper/EmployeeMapper.xml。因此 XML 必须与接口同名,且放在与接口包路径对应的类路径目录下。
Q3:启动时提示 Invalid bound statement (not found),可能的原因有哪些?
A:常见原因包括:① Mapper 未被注册到
mappers中;② XML 的namespace与接口全限定名不匹配;③ XML 中的id与接口方法名不匹配;④ XML 文件未被编译到类路径(如放在java目录下未配置 Maven 资源过滤);⑤ 接口与 XML 不同名或路径不一致(使用package/class时)。
Q4:同一个 Mapper 能否同时用注解和 XML 定义 SQL?
A:可以,但不推荐。如果接口方法上同时存在注解(如
@Select)和 XML 中同id的语句,MyBatis 的加载顺序可能导致覆盖行为不确定。最佳实践是二选一:简单查询用注解,复杂 SQL(动态 SQL、关联映射)用 XML。
小结
mappers 是 MyBatis 全局配置的"收官之笔",它将精心编写的 SQL 映射定义注册到框架中,使其能够被业务代码调用。四种引入方式各有适用场景,但 package 扫描以其批量、自动、低维护的优势成为项目首选。记住:路径正确、namespace 匹配、避免重复注册,是 Mapper 配置的三条铁律。
下一章引子
至此,MyBatis 全局配置文件的十大核心元素已全部拆解完毕。从 properties 的配置外部化,到 settings 的行为总控,再到 mappers 的映射注册,这些元素共同构成了一套完整的 MyBatis 运行环境。在下一章中,我们将离开全局配置,深入 Mapper XML 的世界,学习 resultMap 的高级映射技巧——这是处理复杂关联查询、嵌套结果、鉴别器映射的必备技能。