前言
你是否也有过这样的疑惑:为什么只需要配置一个数据源,写一个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的事务是如何管理的?
这些问题留给读者进一步探索和学习。