Mybatis流程分析(四):Mybatis构建Mapper背后的故事
本系列文章皆在从细节着手,由浅入深的分析Mybatis框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis的工作原理。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜
前言
通过分析我们知道,在Mybatis中SqlSession 是一个用于执行 SQL 操作的核心接口。它提供了与数据库的交互方法,可以执行查询、插入、更新、删除等操作。
截止到目前,我们从使用Mybatis的四行简单代码开始,依次分析了Mybatis中的 配置文件解析、SqlSessionFactory构建、会话管理等内容。
使用Mybatis的四行代码
//<1> 加载配置文件
InputStream is = Resources.getResourceAsStream("mybatis.xml");
//<2> 创建sessionFactory对象
sessionFactory = new SqlSessionFactoryBuilder().build(is);
//<3> 获取sqlSession对象信息
SqlSession session = factoy.openSqlSession()
//<4> 构建映射器的代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
// .....调用UserMapper中定义的方法,执行相关方法信息
接下来,我们将分析其中getMapper的逻辑,也即图中绿色方框中的内容。

在开始分析getMapper开始之前我们先来看看getMapper到底为我们做了哪些事情。
getMapper背后的逻辑
通过观察上述代码,我们注意到在<4> 处调用SqlSession中的getMapper方法,并将一个接口对象的类信息传入,然后就会获得一个该接口的实例对象mapper。进而通过调用UserMapper接口中定义的方法来实现对数据库的操作。
(注:其中UserMapper为数据持久化层中定义的一个接口)
这一现象背后getMapper帮我们完成哪些逻辑呢?事实上,大致概括为如下几点:
- 构建一个实现
UserMapper接口的实例对象 - 可以实现
sql语句与UserMapper中方法的绑定 - 执行与
UserMapper中方法绑定的sql语句
构建Mapper的逻辑
进一步,上述代码调用的getMapper也会在DefaultSqlSession中进行定义,其逻辑如下:
DefaultSqlSessiongetMapper的相关逻辑
public class DefaultSqlSession implements SqlSession {
// .. 省略其他无关代码
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
}
可以看到DefaultSqlSession中的getMapper方法逻辑非常简单。概括来看就是将构建Mapper的逻辑委托给configuration中的getMapper进行实现。而Configuration中getMapper的逻辑如下所示:
Configuration # getMapper
public <T> T getMapper(Class<T> type,
SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
可以看到,Configuration中getMapper的方法又将逻辑委托于mapperRegistry进行实现。
如下这张图对前面不断跳转的getMapper逻辑进行一个简短的总结。

更进一步,MapperRegistry中方法getMapper的逻辑如下所示:
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>,
MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type,
SqlSession sqlSession) {
// 根据传入的class,返回一个MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 利用得到的MapperProxyFactory对象返回一个实例对象
return mapperProxyFactory.newInstance(sqlSession);
}
可以看到在MapperRegistry中,持有一个Map结构,用以实现class 和 MapperProxyFactory之间的映射。其中getMapper的逻辑也非常简洁,大致逻辑可总结为如下所述:
- 根据传入的
class信息获取class对应的MapperProxyFactory对象 - 调用
MapperProxyFactory中的newInstance方法返回一个实例对象
此时,虽然我们不知道newInstance背后做了那些工作,但是我们可以肯定其一定会返回一个实现UserMapper的实例对象。 因为如果此处不进行返回一个实例对象的话,getMapper的调用链还会一直持续下去。但事实并非如此,所以此处一定会返回一个实例对象.
事实上,如果你对Java动态代理熟悉的话,我们可以知道当我们利用Jdk中的Proxy生成动态代理类时会调用Proxy.newInstance()这样一个方法从而获取到接口的代理类信息。
此时我们不免有这样的疑问,Mybatis根据传入的Mapper可以生成一个实现该Mapper的对象。那这一切背后的逻辑是不是也是基于Jdk的动态代理?
带着这样的疑问,我们不妨进入到MapperProxyFactory内部,看看其内部是如何根据接口来构建一个是个实例对象的。
MapperProxyFactory的相关内容
public class MapperProxyFactory<T> {
// 待实现的接口信息
private final Class<T> mapperInterface;
// ....省略其他无关代码
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 动态代理的逻辑
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
可以看到MapperProxyFactory中的newInstance方法背后的逻辑与我们猜测的如出一辙。其生成代理对象逻辑就是通过Jdk中的Proxy来完成的。
至此,我们应该明白getMapper背后的逻辑就是:通过传入Mapper的接口信息,然后利用Jdk动态代理类的方式生成一个实现该Mapper接口的代理对象。
进一步,getMapper中的调用链可总结为如图所示内容。

总结
本章我们以getMapper出发点,分析了getMapper背后的逻辑。基于此我们分析出在Mybatis中构建Mapper时应该考虑如下三点:
本章我们将主要分析了Mybatis中如何根据传入的接口构建实例对象背后的逻辑。一言以概之,其本质就是通过Jdk动态代理的方式返回一个实现接口的实例对象。 只不过在Mybatis中为了实现这一逻辑执行了一系列方法的调用逻辑。而当你明白了Mybatis这一精髓,再回看本文,相信一定会有一种轻舟已过万重山的感觉。
至此,我们就讲清楚了Mybaits中 根据传入接口返回一个实例对象 背后的逻辑。 下一章,我们将主要分析Mybaits是如何将sql语法与接口中方法进行绑定。
转载自:https://juejin.cn/post/7270862391154950199