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
中进行定义,其逻辑如下:
DefaultSqlSession
getMapper
的相关逻辑
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