1、前言
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。我们在使用 Mybaits 进行开发 ,通常只需要定义几个 Mapper 接口,然后在编写一个 xml 文件,我们在配置文件中写好 sql , Mybatis 帮我们完成 Mapper 接口到具体实现的调用。以及将结果映射到 model bean 中。
但是,对于 Mapper 接口,我们并没有编写其实现类!Mybatis是如何找到其实现类,进而完成具体的 CRUD 方法调用的呢?原理何在?
通常我们使用 Mybatis (Mybatis 单独使用,而不是整合 spring )的主要步骤是:
- 构建 SqlSessionFactory ( 通过 xml 配置文件 , 或者直接编写Java代码)
- 从 SqlSessionFactory 中获取 SqlSession
- 从 SqlSession 中获取 Mapper
- 调用 Mapper的方法 ,例如:userMapper.selectUser(int userId);
2、Mapper 接口是怎么找到实现类的
2.1、Mapper 接口注册
诸如UserMapper之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。
2.2、生成Mapper接口的动态代理类
最终是通过Proxy.newProxyInstance产生了一个UserMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:
- ClassLoader —— 指定当前接口的加载器即可
- 当前被代理的接口是什么 —— 这里就是 BlogMapper
- 代理类是什么 —— 这里就是 MapperProxy
代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。
MapperProxy的代码如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 实现了InvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 使用了缓存
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行CURD
return mapperMethod.execute(sqlSession, args);
}
}
再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。
3、总结
Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:
- Mapper 接口在初始SqlSessionFactory 注册的。
- Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class对象; value = 创建当前Mapper的工厂。
- Mapper 注册之后,可以从SqlSession中get
- SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
- 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。