百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

MyBatis代理机制深度解析:从配置到执行的完整流程

ztj100 2025-07-21 19:09 4 浏览 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的事务是如何管理的?


这些问题留给读者进一步探索和学习。

相关推荐

新手学Docker:挂载卷Volumes(docker挂载nfs卷)

默认情况下,在容器内创建的文件,都会保存在容器基于镜像之上的一个可读写层。但是,这一层是依赖于容器而存在的,当容器不存在时,这些数据也就不存在了。而且这些数据紧紧的耦合在容器内部,很难将这些数据导出。...

Docker命令最全详解(39个最常用命令)

Docker是云原生的核心,也是大厂的必备技能,下面我就全面来详解Docker核心命令@mikechen本文作者:陈睿|mikechen文章来源:mikechen.cc一、Docker基本命令...

松勤软件测试:详解Docker,如何用portainer管理Docker容器

镜像管理搜索镜像dockersearch镜像名称拉取镜像dockerpullname[:tag]列出镜像dockerimages删除镜像dockerrmiimage名称或id删除...

【Docker 新手入门指南】第十四章:Docker常用命令

以下Docker常用命令大全,涵盖容器、镜像、网络、数据卷及系统管理等核心操作,结合分类与示例助你快速掌握:一、容器生命周期管理...

Docker 一键清盘术,释放磁盘空间不求人

在日常开发和运维中,Docker容器化让我们的工作更高效,但不知不觉中,磁盘空间却被镜像、容器和卷悄悄吃掉。今天教你一套干净利落的Docker磁盘清理攻略,让你轻松腾出宝贵空间!一、快速查看磁盘...

第六节 Docker 容器核心操作与实践指南

一、镜像与容器的本质关系(一)镜像(Image)的核心特性静态模板:包含应用运行所需的所有依赖(系统库、运行时、配置文件等)不可变性:镜像构建完成后内容不可修改,确保环境一致性...

别再去找Docker命令了,你要的常用的全都在这

Docker常用命令集合给大家介绍一些常用的Docker命令,对你有帮助的同学建议收藏作为一个查询手册哦。Docker容器的一些命令按功能分类大致如下:Docker环境信息info、versio...

Docker 数据持久化最佳实践:Volume 使用全指南

你是不是也经常在使用Docker的时候,遇到“数据持久化”相关的困惑?容器删了,数据也跟着没了?今天就带你深入了解Docker的Volume机制,搞懂什么是Volume、怎么使用、存储在...

【Docker 新手入门指南】第十五章:常见故障排除

一、前期准备:收集关键信息在排查问题前,建议先获取以下系统数据,便于精准定位故障:...

Docker网络与iptables实战指南:从原理到安全加固

一、Docker网络核心原理:容器与iptables的“共生关系”Docker容器的网络能力依赖于Linux内核的两大核心技术:虚拟网络设备(vethpair、网桥)和iptables规则链。以默认...

别再docker exec了!盘点进入容器排错的5种“更优雅”姿势

一、dockerattach:直接“附身”容器的原始方法当容器日志疯狂刷屏时,dockerattach就像直接跳进正在行驶的汽车——能看到仪表盘数据,但方向盘不在你手上。这种直接附加到容器主进程的...

云计算核心技术Docker教程:Docker数据卷的使用

在生产环境中使用Docker,要想实现数据的持久化(所谓Docker的数据持久化即数据不随着Container的结束而结束)或者需要在多个容器之间进行数据共享,需要将数据从宿主机挂载到容器中,这就会...

【开发技术】Mybatis中进行多表关联查询?性能是不是会变好呢?

Mybatis是一种基于Java的持久层框架,能够帮助我们操作数据库。在Mybatis中,进行多表关联的整合查询,需要使用嵌套查询或者使用ResultMap进行映射。下面,我们将从这两个方面来介绍多表...

Mybatis 批量更新数据 Mysql批量更新数据

通常如果需要一次更新多条数据有两个方式,(1)在业务代码中循环遍历逐条更新。(2)一次性更新所有数据1批量更新相同的值不同的条件...

Mybatis 如何批量删除数据(mybatis批量merge)

Mybatis如何批量删除数据本期以最常用的根据id批量删除数据为例:接口设计1:List类型单参数IntegerdeleteByIds(List<Integer>ids);...

取消回复欢迎 发表评论: