MyBatis代理机制深度解析:从配置到执行的完整流程
ztj100 2025-07-21 19:09 14 浏览 0 评论
前言
你是否也有过这样的疑惑:为什么只需要配置一个数据源,写一个XML文件定义SQL,再写一个Mapper接口,MyBatis就能神奇地执行数据库操作?这背后到底发生了什么?
本文将深入分析MyBatis的核心工作原理,揭开Mapper代理机制的神秘面纱。
1. MyBatis核心组件概述
在深入原理之前,我们先了解MyBatis的核心组件:
1.1 核心组件关系图
1.2 以如下项目为例
在你的项目中,我们可以看到这样的结构:
// Mapper接口
public interface UserMapper {
int insert(UserQueryPO reportQueryPO);
// 其他方法...
}
// Repository实现类
@Service
@AllArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final UserMapper userMapper; // 这里就是MyBatis的代理对象
@Override
public boolean insertOrUpdate(User userPO) {
// 调用Mapper方法,实际上调用的是代理对象
int result = userMapper.insert(userPO);
return result > 0;
}
}
2. MyBatis启动流程:从配置到代理
2.1 配置解析阶段
当Spring Boot应用启动时,MyBatis会经历以下步骤:
步骤1:解析配置文件
// MyBatis会解析application.yml中的配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/cbi_report
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
步骤2:构建Configuration对象
public class Configuration {
// 存储所有的配置信息
protected Environment environment; // 数据源和事务管理器
protected MapperRegistry mapperRegistry; // Mapper注册表
protected Map<String, MappedStatement> mappedStatements; // SQL映射语句
// ... 其他配置
}
步骤3:扫描并注册Mapper接口
// MyBatis会扫描@Mapper注解或配置的包路径
@MapperScan("com.cbi.report.engine.infrastructure.persistence.mapper")
public class MyBatisConfig {
// 扫描指定包下的所有Mapper接口
}
2.2 Mapper注册过程
当MyBatis发现`UserMapper`接口时,会执行以下操作:
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 为每个Mapper接口创建一个代理工厂
MapperProxyFactory<T> mapperProxyFactory = new MapperProxyFactory<>(type);
knownMappers.put(type, mapperProxyFactory);
}
}
}
3. 代理对象创建:MapperProxy的诞生
3.1 MapperProxyFactory工厂类
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 创建代理对象的核心方法
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy // InvocationHandler
);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
3.2 MapperProxy代理处理器
这是MyBatis的核心所在:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object类的方法(如toString、equals),直接执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 这里是关键:为每个Mapper方法创建MapperMethodInvoker
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
// 为每个方法创建调用器
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
}
4. 方法调用执行:从接口到SQL
4.1 当你调用Mapper方法时发生了什么
以如下代码为例:
int result = userMapper.insert(userPO);
实际的执行流程是:
Step 1: 代理拦截
// MapperProxy.invoke()被调用
public Object invoke(Object proxy, Method method, Object[] args) {
// method = UserMapper.insert(UserPO)
// args = [userPO]
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
Step 2: MapperMethod执行
public class MapperMethod {
private final SqlCommand command; // SQL命令信息
private final MethodSignature method; // 方法签名信息
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT:
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
case UPDATE:
// ...
case DELETE:
// ...
case SELECT:
// ...
}
return result;
}
}
Step 3: SqlSession执行
// 最终调用SqlSession的insert方法
public int insert(String statement, Object parameter) {
return update(statement, parameter); // INSERT实际上也是update操作
}
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
4.2 Executor执行SQL
public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 准备Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行SQL
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创建PreparedStatement
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置参数
handler.parameterize(stmt);
return stmt;
}
}
5. 完整的调用时序图
5.1 MyBatis执行流程时序图:
5.2 MyBatis核心组件架构图:
5.3 Mapper方法调用生命周期的流程图:
5.4 图表说明
1.时序图 - MyBatis执行流程
展示了从你的代码 userMapper.insert(userPO) 开始,到数据库执行完成的完整时序流程,包含16个关键步骤:
代理拦截 - JDK动态代理拦截方法调用
方法解析 - 确定SQL类型和参数映射
SQL执行 - 通过多层处理器最终执行数据库操作
2.架构图- MyBatis核心组件
展示了MyBatis的整体架构,包括:
应用层 - 你的业务代码和Spring容器
MyBatis核心层 - 配置中心、代理工厂、会话管理 执行层 - SQL执行器和各种处理器
数据层 - 数据源和数据库
3.流程图- Mapper方法调用生命周期
详细展示了一个Mapper方法的完整调用过程:
代理检查 - 验证是否为代理对象 缓存机制 - 方法缓存和性能优化
SQL类型判断 -
INSERT/UPDATE/DELETE/SELECT分支处理
参数处理 - Java对象到SQL参数的转换
实际应用场景
以如下代码为例:
// 这行代码触发的完整流程
int result = userMapper.insert(userPO);
执行路径:
Client → Spring注入代理 → MapperProxy拦截 → 解析INSERT类型 → SqlSession管理 → Executor执行 → StatementHandler处理 → 数据库执行 → 返回影响行数
这些图表帮助你可视化理解:
为什么不需要实现类 - 代理对象替代了实现
如何找到对应的SQL - 通过方法名和配置映射
性能如何优化 - 通过缓存和连接池
事务如何管理 - 通过Executor层统一处理
现在MyBatis的"魔法"应该不再神秘了!每个组件都有明确的职责,通过层层调用最终完成数据库操作。
6. 关键问题解答
6.1 为什么Mapper接口不需要实现类?
因为MyBatis使用JDK动态代理为每个接口创建了代理对象。当你调用接口方法时,实际上调用的是`MapperProxy.invoke()`方法。
6.2 MyBatis如何知道执行哪个SQL?
通过`MappedStatement`对象,它包含了:
- SQL语句ID(通常是接口全名+方法名)
- SQL语句内容
- 参数映射
- 结果映射
6.3 参数是如何传递的?
// 在你的例子中
userMapper.insert(userPO);
// MyBatis会:
// 1. 解析方法参数
// 2. 根据@Param注解或参数位置创建参数映射
// 3. 将Java对象转换为SQL参数
7. MyBatis Plus的增强
在你的项目中使用的是MyBatis Plus,它在MyBatis基础上增加了:
7.1 BaseMapper接口
public interface BaseMapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int updateById(T entity);
T selectById(Serializable id);
// ... 更多通用方法
}
7.2 自动SQL生成
MyBatis Plus可以根据实体类自动生成SQL,无需编写XML:
// 你的Mapper只需要继承BaseMapper
public interface UserMapper extends BaseMapper<UserPO> {
// 自动拥有CRUD方法,无需写SQL
}
8. 实战调试:查看代理对象
你可以通过以下方式验证代理机制:
@Service
@AllArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final UserMapper userMapper;
@Override
public boolean insertOrUpdate(User user) {
// 打印代理对象信息
System.out.println("Mapper类型: " + userMapper.getClass());
System.out.println("是否为代理: " + Proxy.isProxyClass(userMapper.getClass()));
// 执行数据库操作
UserPO userPO = convertToPO(user);
int result = userMapper.insert(userPO);
return result > 0;
}
}
输出结果类似:
Mapper类型: class com.sun.proxy.$Proxy123
是否为代理: true
9. 总结
MyBatis的"魔法"实际上是通过以下技术实现的:
1. **JDK动态代理**:为Mapper接口创建代理对象
2. **反射机制**:解析接口方法和注解
3. **XML解析**:解析SQL映射文件
4. **JDBC封装**:处理数据库连接和SQL执行
整个流程可以概括为:
配置解析 → Mapper注册 → 代理创建 → 方法拦截 → SQL执行 → 结果返回
当你理解了这个原理后,MyBatis就不再神秘了。它只是将复杂的JDBC操作进行了优雅的封装,让开发者可以专注于业务逻辑而不是技术细节。
---
**扩展思考**:
- 为什么MyBatis选择JDK动态代理而不是CGLIB?
- MyBatis的一级缓存和二级缓存是如何实现的?
- 在分布式环境下,MyBatis的事务是如何管理的?
这些问题留给读者进一步探索和学习。
相关推荐
- Linux集群自动化监控系统Zabbix集群搭建到实战
-
自动化监控系统...
- systemd是什么如何使用_systemd/system
-
systemd是什么如何使用简介Systemd是一个在现代Linux发行版中广泛使用的系统和服务管理器。它负责启动系统并管理系统中运行的服务和进程。使用管理服务systemd可以用来启动、停止、...
- Linux服务器日常巡检脚本分享_linux服务器监控脚本
-
Linux系统日常巡检脚本,巡检内容包含了,磁盘,...
- 7,MySQL管理员用户管理_mysql 管理员用户
-
一、首次设置密码1.初始化时设置(推荐)mysqld--initialize--user=mysql--datadir=/data/3306/data--basedir=/usr/local...
- Python数据库编程教程:第 1 章 数据库基础与 Python 连接入门
-
1.1数据库的核心概念在开始Python数据库编程之前,我们需要先理解几个核心概念。数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,它就像一个电子化的文件柜,能让我们高效...
- Linux自定义开机自启动服务脚本_linux添加开机自启动脚本
-
设置WGCloud开机自动启动服务init.d目录下新建脚本在/etc/rc.d/init.d新建启动脚本wgcloudstart.sh,内容如下...
- linux系统启动流程和服务管理,带你进去系统的世界
-
Linux启动流程Rhel6启动过程:开机自检bios-->MBR引导-->GRUB菜单-->加载内核-->init进程初始化Rhel7启动过程:开机自检BIOS-->M...
- CentOS7系统如何修改主机名_centos更改主机名称
-
请关注本头条号,每天坚持更新原创干货技术文章。如需学习视频,请在微信搜索公众号“智传网优”直接开始自助视频学习1.前言本文将讲解CentOS7系统如何修改主机名。...
- 前端工程师需要熟悉的Linux服务器(SSH 终端操作)指令
-
在Linux服务器管理中,SSH(SecureShell)是远程操作的核心工具。以下是SSH终端操作的常用命令和技巧,涵盖连接、文件操作、系统管理等场景:一、SSH连接服务器1.基本连接...
- Linux开机自启服务完全指南:3步搞定系统服务管理器配置
-
为什么需要配置开机自启?想象一下:电商服务器重启后,MySQL和Nginx没自动启动,整个网站瘫痪!这就是为什么开机自启是Linux运维的必备技能。自启服务能确保核心程序在系统启动时自动运行,避免人工...
- Kubernetes 高可用(HA)集群部署指南
-
Kubernetes高可用(HA)集群部署指南本指南涵盖从概念理解、架构选择,到kubeadm高可用部署、生产优化、监控备份和运维的全流程,适用于希望搭建稳定、生产级Kubernetes集群...
- Linux项目开发,你必须了解Systemd服务!
-
1.Systemd简介...
- Linux系统systemd服务管理工具使用技巧
-
简介:在Linux系统里,systemd就像是所有进程的“源头”,它可是系统中PID值为1的进程哟。systemd其实是一堆工具的组合,它的作用可不止是启动操作系统这么简单,像后台服务...
- Linux下NetworkManager和network的和平共处
-
简介我们在使用CentoOS系统时偶尔会遇到配置都正确但network启动不了的问题,这问题经常是由NetworkManager引起的,关闭NetworkManage并取消开机启动network就能正...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
Linux下NetworkManager和network的和平共处
-
Kubernetes 高可用(HA)集群部署指南
-
- 最近发表
-
- Linux集群自动化监控系统Zabbix集群搭建到实战
- systemd是什么如何使用_systemd/system
- Linux服务器日常巡检脚本分享_linux服务器监控脚本
- 7,MySQL管理员用户管理_mysql 管理员用户
- Python数据库编程教程:第 1 章 数据库基础与 Python 连接入门
- Linux自定义开机自启动服务脚本_linux添加开机自启动脚本
- linux系统启动流程和服务管理,带你进去系统的世界
- CentOS7系统如何修改主机名_centos更改主机名称
- 前端工程师需要熟悉的Linux服务器(SSH 终端操作)指令
- Linux开机自启服务完全指南:3步搞定系统服务管理器配置
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)