MyBatis底层原理深度解析:动态代理与注解如何实现ORM映射
一、引言
MyBatis作为一款优秀的ORM框架,其核心设计思想是通过动态代理和注解将接口方法与SQL操作解耦。开发者只需定义Mapper接口并添加注解,便能实现数据库操作,这背后隐藏着精妙的动态代理机制与源码设计。本文将从源码层解析MyBatis如何实现这一过程。
二、动态代理机制:从接口到实现类
关键点:MyBatis通过JDK动态代理为Mapper接口生成代理对象,拦截所有方法调用,将其路由到SQL执行逻辑。
1. Mapper接口代理的创建
// 用户定义的Mapper接口
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(int id);
}
// 获取Mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
源码入口:SqlSession#getMapper()
调用MapperRegistry.getMapper()
,最终通过MapperProxyFactory
创建代理。
// MapperProxyFactory核心代码
public class MapperProxyFactory<T> {
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}
- MapperProxy:实现
InvocationHandler
接口,拦截所有方法调用。
2. 方法拦截与路由
当调用userMapper.selectById(1)
时,实际进入MapperProxy.invoke()
:
// MapperProxy#invoke 简化逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isDefault()) { /* 处理默认方法 */ }
// 将方法封装为MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
- MapperMethod:封装了SQL类型(SELECT/INSERT)、方法签名和返回类型。
三、注解解析:从注解到MappedStatement
关键流程:启动时解析Mapper接口的注解,生成MappedStatement
,存储SQL和映射信息。
1. 注解解析入口
MapperAnnotationBuilder
类负责解析接口方法上的注解:
// MapperAnnotationBuilder#parse 核心逻辑
public void parse() {
for (Method method : type.getMethods()) {
parseStatement(method); // 解析每个方法
}
}
private void parseStatement(Method method) {
final SqlCommandType sqlCommandType = getSqlCommandType(method);
// 解析@Select、@Insert等注解
final String sql = getSqlAnnotation(method);
// 构建MappedStatement
builderAssistant.addMappedStatement(/* ... */);
}
2. 构建MappedStatement
每个方法对应一个MappedStatement
,包含:
- id:接口全限定名 + 方法名
- sqlSource:解析后的SQL(含动态标签)
- resultMaps:结果集映射配置
四、SQL执行与结果映射
1. 执行入口:MapperMethod.execute()
根据SQL类型调用SqlSession
对应方法:
// MapperMethod#execute 简化逻辑
public Object execute(SqlSession sqlSession, Object[] args) {
switch (command.getType()) {
case SELECT:
if (method.returnsVoid()) { /* ... */ }
else {
Object param = method.convertArgsToSqlCommandParam(args);
return sqlSession.selectOne(command.getName(), param);
}
case INSERT: /* ... */
}
}
2. 参数绑定与动态SQL
- 参数处理:使用
ParamNameResolver
解析方法参数名(支持@Param注解)。 - 动态SQL:
SqlSource
解析包含<if>
,<foreach>
等标签的SQL,生成可执行的SQL字符串。
3. 结果集映射
核心类:DefaultResultSetHandler
// 结果映射核心逻辑
List<Object> resultList = new ArrayList<>();
while (rs.next()) {
Object resultObject = createResultObject(rs, resultMap, lazyLoader);
// 通过反射或TypeHandler填充属性
applyPropertyMappings(rs, resultObject);
resultList.add(resultObject);
}
return resultList;
- TypeHandler:处理类型转换(如String转Date)。
- MetaObject:通过反射操作对象属性。
五、动态代理与注解设计的优势
- 解耦接口与实现:开发者只需关注接口定义,无需编写实现类。
- 灵活的SQL管理:注解与XML配置互补,支持动态SQL。
- 性能优化:代理对象和MappedStatement在启动时初始化,运行时直接调用。
六、总结
通过动态代理和注解,MyBatis实现了接口方法与SQL操作的优雅映射:
- 动态代理:
MapperProxy
拦截方法调用,路由到SqlSession
。 - 注解解析:启动时构建
MappedStatement
,存储SQL元数据。 - 结果映射:结合反射与TypeHandler,完成结果集到Java对象的转换。
这种设计在保持灵活性的同时,极大简化了数据库操作代码,体现了MyBatis“约定优于配置”的核心思想。
源码分析要点:
- 代理生成:
MapperProxyFactory
、MapperProxy
- 注解解析:
MapperAnnotationBuilder
- SQL执行:
MapperMethod
、SqlSessionTemplate
- 结果处理:
DefaultResultSetHandler
通过深入源码,开发者可以更好地理解MyBatis的设计哲学,并针对复杂场景进行定制优化。