一、根据XML配置文件构建SqlSessionFactory

一、首先读取类路径下的配置文件,获取其字节输入流。

二、创建SqlSessionFactoryBuilder对象,调用内部的build方法。factory = new SqlSessionFactoryBuilder().build(in);

三、根据字节输入流创建XMLConfigBuilder即解析器对象parser。XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//根据字节输入流创建XMLConfigBuilder即解析器对象parser
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//返回的Configuration配置对象作为build的参数
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

四、调用parser对象的parse方法,parser.parse(),该结果将返回一个Configuration配置对象,作为build方法的参数。

五、parse()方法中,调用parseConfiguration方法将Configuration元素下的所有配置信息封装进Parser对象的成员Configuration对象之中。

  public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//将configuration的配置信息一一封装到configuration中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

六、其中进行解析xml元素的方式是将通过evalNode方法获取对应名称的节点信息。如:parseConfiguration(parser.evalNode("/configuration"));,此时parser.evalNode("/configuration")即为Configuration下的所有信息。

七、parseConfiguration方法相当于将里面每个元素的信息都单独封装到Configuration中。

值得一提的是,我们之后要分析基于代理模式产生dao的代理对象涉及到mappers的封装,其实也在配置文件读取封装的时候就已经完成,也就是在parseConfiguration方法之中:mapperElement(root.evalNode("mappers"));。他的作用就是,读取我们主配置文件中<mappers>的元素内容,也就是我们配置的映射配置文件。

    <!-- 配置映射文件的位置 -->
<mappers>
<package name="com.smday.dao"></package>
</mappers>

private void mapperElement(XNode parent)方法将mappers配置下的信息获取,此处获取我们resources包下的com.smday.dao包名。

接着就调用了configuration的addMappers方法,其实还是调用的是mapperRegistry。

  public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}

读到这里,我们就会渐渐了解MapperRegistry这个类的职责所在,接着来看,这个类中进行的一些工作,在每次添加mappers的时候,会利用ResolverUtil类查找类路径下的该包名路径下,是否有满足条件的类,如果有的话,就将Class对象添加进去,否则报错。



紧接着,就到了一步比较重要的部分,当然只是我个人觉得,因为第一遍看的时候,我没有想到,这步居然可以封装许许多多的重要信息,我们来看一看:

  public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
//如果已经绑定,则抛出异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//将接口类作为键,将MapperProxyFactory作为值存入
knownMappers.put(type, new MapperProxyFactory<T>(type));
// 在运行解析器之前添加类型十分重要,否则可能会自动尝试绑定映射器解析器
// 如果类型已知,则不会尝试
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析mapper映射文件,封装信息
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

映射配置文件的读取依靠namespace,我们可以通过查看源码发现读取映射配置文件的方法是loadXmlResouce(),所以namespace命名空间至关重要:

  private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
// 防止加载两次,可以发现这句 判断在许多加载资源文件的时候出现
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//最终解析
xmlParser.parse();
}
}
}
  //xmlPaser.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//读取映射配置文件信息的主要代码
configurationElement(parser.evalNode("/mapper"));
//加载完成将该路径设置进去,防止再次加载
configuration.addLoadedResource(resource);
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

可以看到,对映射文件解析之后,mappedStatements对象中出现了以下内容:

至此,主配置文件和映射配置文件的配置信息就已经读取完毕。

八、最后依据获得的Configuration对象,创建一个new DefaultSqlSessionFactory(config)

  public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

总结:

  • 解析配置文件的信息,并保存在Configuration对象中。

  • 返回包含Configuration的DefaultSqlSession对象。

二、通过SqlSessionFactory创建SqlSession

一、调用SqlSessionFactory对象的openSession方法,其实是调用private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit)方法,通过参数就可以知道,分别是执行器的类型,事务隔离级别和设置是否自动提交,因此,我们就可以得知,我们在创建SqlSession的时候可以指定这些属性。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获取Environment信息
final Environment environment = configuration.getEnvironment();
//获取TransactionFactory信息
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//创建Transaction对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建执行器对象Executor
final Executor executor = configuration.newExecutor(tx, execType);
//创建DefaultSqlSession对象并返回
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

二、从configuration中获取environment、dataSource和transactionFactory信息,创建事务对象Transaction。

补充:后续看了一些博客,说是保证executor不为空,因为defaultExecutorType有可能为空。

三、根据配置信息,执行器信息和自动提交信息创建DefaultSqlSession。

三、getMapper获取动态代理对象

下面这句话意思非常明了,就是通过传入接口类型对象,获取接口代理对象。

IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);

具体的过程如下:

一、首先,调用SqlSession的实现类DefaultSqlSession的getMapper方法,其实是在该方法内调用configuration的getMapper方法,将接口类对象以及当前sqlsession对象传入。

  //DefaultSqlSession.java
@Override
public <T> T getMapper(Class<T> type) {
//调用configuration的getMapper
return configuration.<T>getMapper(type, this);
}

二、接着调用我们熟悉的mapperRegistry,因为我们知道,在读取配置文件,创建sqlSession的时候,接口类型信息就已经被存入到其内部维护的Map之中。

  //Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession);
}



三、我们来看看getMapper方法具体的实现如何:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根据传入的类型获取对应的键,也就是这个代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//最终返回的是代理工厂产生的一个实例对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

四、紧接着,我们进入MapperProxyFactory,真真实实地发现了创建代理对象的过程。

  protected T newInstance(MapperProxy<T> mapperProxy) {
//创建MapperProxy代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
//MapperProxy是代理类,
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

mybatis源码学习:从SqlSessionFactory到代理对象的生成的更多相关文章

  1. mybatis源码分析(四)---------------代理对象的生成

    在mybatis两种开发方式这边文章中,我们提到了Mapper动态代理开发这种方式,现在抛出一个问题:通过sqlSession.getMapper(XXXMapper.class)来获取代理对象的过程 ...

  2. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  3. myBatis源码学习之SqlSessionFactory

    上一篇博客 SqlSessionFactoryBuilder 中介绍了它的作用就是获得DefaultSqlSessionFactory SqlSessionFactory是一个接口,其具体实现类是De ...

  4. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  5. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

  6. mybatis源码学习:一级缓存和二级缓存分析

    目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...

  7. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  8. Mybatis源码学习之整体架构(一)

    简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...

  9. mybatis源码分析(1)——SqlSessionFactory实例的产生过程

    在使用mybatis框架时,第一步就需要产生SqlSessionFactory类的实例(相当于是产生连接池),通过调用SqlSessionFactoryBuilder类的实例的build方法来完成.下 ...

  10. Spring mybatis源码学习指引目录

    前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...

随机推荐

  1. python之装饰器的进阶

    一.带参数的装饰器 (必须会) 针对不同的app的验证,比如:天猫和天猫超市,京东商城和京东超市 def wrapper_out(n): print(n) def wrapper(f): def in ...

  2. 【干货】Keras学习资源汇总

    目录: Keras简介 Keras学习手册 Keras学习视频 Keras代码案例 Keras&NLP Keras&CV Keras项目 一.Keras简介 Keras是Python中 ...

  3. 2020 | 可替代Selenium的测试框架Top15

    本文首发于 微信公众号: 软测小生 Selenium是一种开源自动测试工具.它可以跨不同的浏览器和平台在Web应用程序上执行功能,回归,负载测试.Slenium是最好的工具之一,但确实有一些缺点. 业 ...

  4. SpringCloud-Nacos/OpenFien/Gateway的基本介绍及快速上手

    一.Spring-Cloud-Alibaba-Nacos 注册中心 1.下载.安装 Nacos 下载地址:https://github.com/alibaba/nacos/releases 下载后解压 ...

  5. python数据库MySQL之视图,触发器,事务,存储过程,函数

    一 视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的 ...

  6. 万物互联—MQTT协议

    一.简介 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输),最早是1999年由IBM开发的基于发布/订阅范式的消息协议,是一种极其简单和轻量级的消息 ...

  7. ubuntu16.04下安装java8

    从Oracle官网下载jdk,jdk-8u231-linux-x64.tar.gz (1)复制到/opt 目录内 sudo cp jdk-8u231-linux-x64.tar.gz /opt (3) ...

  8. C++编译/编辑器对OIer的必要功能

    (没有引战的意思,如果有不同意见可以评论区发言,只是写出我目前的情况) 作为一个C++ OIer肯定是用过Dev的,因为学校推荐啊我也没有办法.都知道Dev又丑又没有代码补全,但是却是最最最适合OIe ...

  9. Maven多模块项目+MVC框架+AJAX技术+layui分页对数据库增删改查实例

    昨天刚入门Maven多模块项目,所以简单写了一个小测试,就是对数据库单表的增删改查,例子比较综合,写得哪里不妥还望大神赐教,感谢! 首先看一下项目结构: 可以看到,一个项目MavenEmployee里 ...

  10. .Net微服务实践(五)[服务发现]:Consul介绍和环境搭建

    目录 介绍 服务发现 健康检查.键值存储和数据中心 架构 Consul模式 环境安装 HTTP API 和Command CLI 示例API介绍 最后 在上篇.Net微服务实践(四)[网关]:Ocel ...