【Mybatins】源码解析与执行流程

1.定位到mybatis-config.xml并读取装载。获取输入流InputStream。

2.解析输入流InputStream,把mybatis-config.xml配置文件中相关配置项解析,校验,保存起来。

3.创建sqlSessionFactory对象,在我们的印象里,session就是一次会话,所以我们可以理解sqlSessionFactory就是个工厂类,就专门创建sqlSession对象,并且这个sqlSessionFactory工厂类是唯一不变的(单例)。

4.创建sqlSession,SqlSession中肯定保存了配置文件内容信息和执行数据库相关的操作。

5.获取userMapper对象,但是UserMapper是接口,并且没有实现类。怎么就可以调用其方法呢?这里猜想可能用到了动态代理。

6.userMapper接口中的方法是如何关联到SQL的,这个猜想可能是有个专门映射的类,另外,肯定使用到了接口全路径名+方法名称,这个才能确保方法和SQL关联(主要是使用的时候,都是方法名必须和SQL中statementId一致,由此猜想的)

7.最后底层使用JDBC去操作数据库。

8.作为一个持久化框架,很有可能会使用到缓存,用来存储每次查询数据。

仅仅是个人假设不清楚源码的情况,仅仅从这个简单的案例出发的,案例中没有差距、缓存,但是下面源码分析中是有的。

===================获取:MyBatins-confgi.xml文件=====================

1:读取mybatis-config.xml配置文件,这里我们就没必要阅读这部分源码了,这里得到是InputStream输入流。接下来就是基于这个输入流进行一系列牛逼的操作。

2:获取到我们的MyBatins-config-xml配置文件后,进行内容的读取并且最后转换为Configuration进行保存。

==========================================================

============SqlSessionFactoryBuilder(执行)=============

==========================================================

2:SqlSessionFactorysqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

3:SqlSessionFactory没有构造方法,那么这里使用的就是默认无参构造方法,所以我们直接进去build方法。

注意:SqlSessionFactory中提供了三种读取配置信息的方法后:字节流、字符流和Configuration配置类。

4:传入一个Configuration对象并实例化DefaultSqlSessionFactory类并返回类型为SqlSessionFactory

==========================================================

============SqlSessionFactoryBuilder(执行)=============

==========================================================

1: XMLConfigBuilder 解析Mybatis配置文件

2: XMLMapperBuilder 解析Mapper映射文件

3: XMLStatementBuilder 解析Mapper映射文件中的insert update delete select节点

4: MapperBuilderAssistant 解析Mapper映射文件的辅助类

5: XMLScriptBuilder 解析SQL语句 处理动态SQL

6: MappedStatement 用来表示insert updatedelete select节点的数据

7: ResultMapping 存储resultMap节点下的子节点 一个子节点会封装成一个该对象

8: ResultMapresultMap对应的Java类

9: SqlSource 用来表示一条SQL语句

10:SqlNode 用来表示语句中的动态SQL

==========================================================

============SqlSessionFactoryBuilder(解析配置文件)============

==========================================================

1:解析配置文件

2:生成Configuration对象

3:传入DefaultSqlSessionFactory类并实例化返回SqlSessionFactory

4:创建sqlSessionFactory对象,在我们的印象里,session就是一次会话,所以我们可以理解sqlSessionFactory就是个工厂类,就专门创建sqlSession对象,并且这个sqlSessionFactory工厂类是唯一不变的(单例)

==========================================================

====SqlSessionFactory构建SqlSession(获取会话)===

==========================================================

1:SqlSession sqlSession =sqlSessionFactory.openSession();

2:SqlSessionFactory是DefaultSqlSessionFactory。那么此时调用的openSession()方法为DefaultSqlSessionFactory中的方法。

===============================================

=======TransactionFactory(详解)========

===============================================

2:TransactionFactory transactionFactory =this.getTransactionFactoryFromEnvironment(environment);

3:TransactionFactory接口对应的实现类UML图

3.1:JdbcTransactionFactory生产JdbcTransaction如果配置的JDBC,则会使用Connection对象的commit()、rollback()、close()方法来管理事务。

3.2:ManagedTransactionFactory生产ManagedTransaction如果我们配置的是MANAGED,会把事务交给容器来管理,比如JBOSS,Weblogic。因为我们是本地跑的程序,如果配

置成MANAGED就会不有任何事务

3.3:

===============================================

=======Executor(详解)========

===============================================

1: Executorexecutor = this.configuration.newExecutor(tx, execType)

BaseExecutor实现Executor接口,然后让具体实现类继承抽象类呢?这就是模板方法模式的实现

模板方法模式就是定义一个算法骨架,并允许子类为一个或者多个步骤提供实现。模板方法是得子类可以再不改变算法结构的情况下,重新定义算法的某些步骤

抽象方法是在子类汇总实现的,每种执行器自己实现自己的逻辑,BaseExecutor最终会调用到具体的子类

2:使用装饰器设计模式对Executor进行装饰,执行器链对所有的代理进行拦截????????

===============================================

=======DefaultSqlSession(详解)========

===============================================

DefaultSqlSession(this.configuration,executor, autoCommit);

1:Configuration(对象)

2:executor(对象)

3:自动提交

===============================================

=======DefaultSqlSession(主要属性)========

===============================================

DefaultSqlSession中包含两个重要属性:

===============================================

======SqlSession构建流程图========

===============================================

==========================================================

==========SqlSession调用(getMapper)============

==========================================================

1:Configuration中的getMapper方法。然后我们进入Configuration中的getMapper方法(传递当前的Mapper与当前的SqlSession)对象。

2:MapperProxyFactory对象里保存了mapper接口的class对象,就是一个普通的类,没有什么逻辑。

3:MapperProxy类实现了InvocationHandler,Serializable的接口,所以(Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), newClass[]{this.mapperInterface}, mapperProxy))

总结:GetMapper方法返回的是一个JDK动态代理对象(类型是$Proxy+数字)。这个代理对象会继承Proxy类,实现被代理的接口UserMpper,里面持有了一个MapperProxy类型的触发管理类。最后返回的userMapper就是MapperProxyFactory的创建的代理对象,然后这个对象中包含了MapperProxy对象

===============================================

======Mapper.java找到Mapper.xml(?)========

===============================================

最后我们调用userMapper.selectUserById(),本质上调用的是MapperProxy的invoke()方法。

注意点: 如果根据(接口+方法名找到Statement ID ),这个逻辑在InvocationHandler子类(MapperProxy类)中就可以完成了,其实也就没有必要在用实现类了。

===============================================

======获取Mapper整个流程========

===============================================

我们已经拿到了UserMapper接口的代理对象。接下来我们就去调用这个代理对象的方法。

==========================================================

=========Mapper中的方法与SQL关联============

==========================================================

1:通过前面的分析,我们已经知道了userMapper是通过动态代理生成的代理对象,所以调用这个代理对象的任意方法都是执行触发管理类MapperProxy的invoke()方法

2:第一部分:MapperProxy.invoke()到Executor.query(方法和SQL关联)(UserMapper类中的方法已经和UserMapper.xml中的sql给彻底关联起来了)

第二部分:Executor.query到JDBC中的SQL执行

MapperProxy.invoke():先来看看这个invoke()方法里有些什么逻辑。

从缓存获取MapperMethod,这里加入了缓存主要是为了提升MapperMethod的获取速度。这个设计非常的有意思,缓存的使用在Mybatis中也是非常之多。

Map的computeIfAbsent方法:根据key获取值,如果值为null,则把后面的Object的值付给key。

继续看MapperMethod这个类,定义了两个属性command和method,以及两个静态内部类。

SqlCommand封装了statementID和SQL类型,比如说:

另外还有个属性MethodSignature,主要是封装的是返回值的类型和方法入参。这里我们debug看看这个MapperMethod对象返回的内容和我们案例中代码的关联。

MapperMethod.execute:先来看看这个方法的整体逻辑:

这个方法中,根据我们上面获得的不同的type(INSERT、UPDATE、DELETE、SELECT)和返回类型:

1.调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。2.调用sqlSession的insert()、update()、dalete()、selectOne()方法。我们这个案例是查询,这里回到了DefaultSqlSession中selectOne方法中。

SqlSession.selectOne方法

继续DefaultSqlSession中的selectOne()方法:

这里调用的是selectList方法。

在这个方法里是根据statement从configuration对象中获取MappedStatement。

在configuration中getMappedStatement方法:

总结:我们UserMapper类中的方法已经和UserMapper.xml中的sql给彻底关联起来了

================================================================

============Executor.query到JDBC中的SQL执行==============

=================================================================

Executor.query()方法

Executor对象是在调用openSession方法的时候创建的

CachingExecutor.query()

在CachingExecutor中

BoundSql中主要是SQL和参数:

既然是缓存,我们肯定想到key-value数据结构。

下面来看看这个key生成规则:

这个二级缓存是怎么构成的呢?并且还要保证在查询的时候必须是唯一

也就说,构成key主要有:

方法相同、翻页偏移量相同、SQL相同、参数相同、数据源环境相同才会被认为是同一个查询。

这里大家知道这个层面就已经阔以了。如果向更深入的搞,就得把hashCode这些扯进来了,请看上面这个张图里前面的几个属性。

处理二级缓存

首先是从ms中取出cache对象,判断cache对象是否为null,如果为null,则没有查询二级缓存和写入二级缓存的流程。

那么这个Cache对象是什么创建的呢?

二级缓存如何开启?

配置项:

cacheEnabled=true表示二级缓存可用,但是要开启话,需要在Mapper.xml内配置。

对配置项属性说明:

1:flushInterval="60000",间隔60秒清空缓存,这个间隔60秒,是被动触发的,而不是定时器轮询的。

2:size=512,表示队列最大512个长度,大于则移除队列最前面的元素,这里的长度指的是CacheKey的个数,默认为1024。

3:readOnly="true",表示任何获取对象的操作,都将返回同一实例对象。如果readOnly="false",则每次返回该对象的拷贝对象,简单说就是序列化复制一份返回。

4:eviction:缓存会使用默认的Least RecentlyUsed(LRU,最近最少使用的)算法来收回。FIFO:First In First Out先进先出队列。

在解析Mapper.xml的XMLMapperBuilder类中的cacheElement()方法里。

解析二级缓存中的标签:

创建Cache对象:

二级缓存处理完了,就来到BaseExecutor的query方法中。

BaseExecutor.query()

第一步,清空缓存

queryStack用于记录查询栈,防止地柜查询重复处理缓存。

flushCache=true的时候,会先清理本地缓存(一级缓存)。

如果没有缓存会从数据库中查询

list =queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

在看看这个方法的逻辑:

先在缓存使用占位符占位,然后查询,移除占位符,将数据放入一级缓存中。

执行Executor的doQuery()方法,默认使用SimpleExecutor

SimpleExecutor.doQuery

下面就来到了SimpleExecutor中的doQuery方法。

创建StatementHandler

在configuration中newStatementHandler()里,

创建了一个StatementHandler,先得到RoutingStatementHandler(路由)。

RoutingStatementHandler创建的时候是用来创建基本的StatementHandler的。这里会根据MapperStament里面的statementType决定StatementHandler类型。

默认是PREPARED。

StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultHandler。

上面说的这几个对象正式被插件拦截的四大对象,所以在创建的时都要用拦截器进行包装的方法。

对于插件相关的,请看前面发的插件的文章。

创建Statement

创建对象后就会执行RoutingStatementHandler的query方法

这里设计很有意思,所有的处理都要使用RoutingStatementHandler来路由,全部通过委托的方式进行调用。

然后执行到PreparedStatementHandler中的query方法。

到了ps.execute();表示已经到JDBC层面了,这时候SQL就已经执行了。后面就是调用DefaultResultSetHandler类进行处理。

到这里,SQL语句就执行完毕,并将结果集赋值并返回了

===============================================

======Mapper调用指定执行方法整个流程图========

===============================================

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

duguoqing_not

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值