MyBatis架构原理

MyBatis架构原理


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


一、架构设计

请添加图片描述
可以将MyBatis的功能架构分为三层

  1. API接口层:提供给外部使用的接口API,开发人员可以通过这些本地API来操纵数据库。接口层一旦接收到这些调用请求,那么就会调用数据处理层来完成具体的数据处理。
    • MyBatis和数据库的交互有两种方式:
      1. 使用传统的MyBatis提供的API;例如sqlSession提供的selectLIst、selectOne等。
      2. 使用Mapper代理的方式(常用)
  2. 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。主要目的是根据调用的请求完成一次数据库操作。
  3. 基础支撑层:包括连接管理、事务管理、配置加载、缓存处理,都是作为公用的功能抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

二、主要构件及其相互关系

构件描述
SqlSession作为MyBatis⼯作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
ExecutorMyBatis执⾏器,是MyBatis调度的核⼼,负责SQL语句的⽣成和查询缓存的维护
StatementHandler封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler负责对⽤户传递的参数转换成JDBC Statement所需要的参数,
ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatementMappedStatement维护了⼀条<select update delete insert>节点的封装
SqlSource负责根据⽤户传递的parameterObject,动态地⽣成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql表示动态⽣成的SQL语句以及相应的参数信息

请添加图片描述

三、总体流程

1. 加载配置文件并初始化

触发条件:加载配置⽂件
配置来源于两个地⽅,⼀个是配置⽂件(主配置⽂件conf.xml,*mapper.xml),—个是java代码中的注解(例如使用注解开发的SQL信息@select),将主配置⽂件内容解析封装到Configuration,将sql的配置信息加载成为⼀个mappedstatement对象,存储在内存之中。

2. 接收调用请求

触发条件:调⽤Mybatis提供的API
传⼊参数:为SQL的StatementId和传⼊参数对象
处理过程:将请求传递给下层的请求处理层进⾏处理。

3. 处理操作请求

触发条件:API接⼝层传递请求过来
传⼊参数:为SQL的ID和传⼊参数对象

处理过程
(A) 根据SQL的StatementId查找对应的MappedStatement对象。
(B) 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
© 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
(D) 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理结果。
(E) 释放连接资源。

4. 返回处理结果

将最终的处理结果返回

四、源码分析

使用传统模式(SqlSession)

在这里插入图片描述

1. 加载配置文件并初始化

① 加载配置文件

获取到对应的MyBatis核心配置文件地址,加载为字节输入流并返回
在这里插入图片描述

在这里插入图片描述

② 解析、初始化配置文件
解析配置文件

使用XPathParser解析核心配置文件的字节输入流
在这里插入图片描述
在这里插入图片描述

解析配置文件后封装

这里主要是看parser.parse()方法,这里将解析出来的核心配置封装成configuration对象

先判断该配置文件是否解析过,如果解析过那么抛出异常;没有解析那么先设置为true(意为已解析)后再解析XML配置文件中的configuration节点并返回对应的configuration。
在这里插入图片描述
configuration标签中的各类子标签节点进行解析,然后封装到Configuration对象
在这里插入图片描述
这里以properties标签为例:
获取其子标签节点,先判断子标签中的resourceurl两属性不能共存,共存则抛出异常;解析完成后将对应的数据存放再configuration对象和XPathParser对象(parser)中。
在这里插入图片描述
再看看mappers中的封装:
可以点进Configuration对象中查看,很明显可以得知mappedStatements的底层就是map,其中key是${namespace}.${id}组合而成,并找到对应的MappedStatement对象
在这里插入图片描述

每条语句只对应一个MappedStatement对象
在这里插入图片描述
最后,解析之后的Configuration对象会传入SqlSessionFactory.build()方法中并获取对应的SqlSessionFactory对象,由源码可得默认的SqlSessionFactory对象为DefaultSqlSessionFactory。
:这里使用了建造者模式
在这里插入图片描述

2. SQL执行流程

① 创建SqlSession

在这里插入图片描述

首先SqlSession中最重要的两个属性,一个是Configuration,一个是Executor。
在这里插入图片描述
点击sqlSessionFactory.openSession()方法进入到创建SqlSession中,这里通过DefaultSqlSessionFactory创建的SqlSession,所以进入到DefaultSqlSession类中。发现创建SqlSession是由三个参数进行创建,首先第一个参数表明的是创建Executor的类型;第二个参数为事务的级别;第三个参数为事务是否自动开启,这里默认为false不开启。
在这里插入图片描述

在这里插入图片描述
创建的SqlSession中的Executor类型为SIMPLE。
在这里插入图片描述
执行器Executor共有三个类型:SIMPLE,REUSE,BATCH
在这里插入图片描述

使用传入的三个参数创建SqlSession,需要创建了事务,且为不自动提交;然后完成了Executor对象的创建。
在这里插入图片描述

② Executor源码

创建了Executor封装到对应的SqlSession对象中后,这时候可以使用sqlSession.selectOne进行调用。
在这里插入图片描述
selectOne底层实际调用的是selectList方法。
在这里插入图片描述
进入SqlSession.selectList()中重载了由三个参数的方法,其中第一个是statementId参数,第二个查询需要传递的参数对象,第三个参数和分页有关,这里不传参的时候为默认值。
在这里插入图片描述
进入到重载的selectList方法中,先根据statementId在Configuration对象中找到对应的MappedStatement对象,然后将对应传给Executor对象执行查询query操作。executor.query()方法中四个参数,第一个为传入的MappedStatement对象,第二个为传入的参数对象(如果是collection集合需要转换为对应的map),第三个参数为默认的分页参数,第四个为ResultSetHandler(默认为null)
在这里插入图片描述
这里传入的ResultSetHandler对象默认为null
在这里插入图片描述
进入SimpleExecutor中的query方法,线程通过MappedStatement对象获取到对应的绑定SQL对象(BoundSql),并且创建一级缓存中的key。

在这里插入图片描述
绑定SQL对象(BoundSql)中包含了已经将占位符处理为?的SQL语句,参数映射数组,参数对象等信息。
在这里插入图片描述
将获取到的参数传入给重载方法query中。

  1. 先判断Executor对象是否关闭,如果关闭则抛出异常。
  2. 如果配置了isFlushCacheRequired参数且查询栈queryStack为0,那么清除本地的一级缓存。
  3. 根据缓存key从本地缓存中查询对应的数据,如果没有,那么从数据库查询获取对应的返回数据(此时的ResultSetHandler为null)
  4. 后续和延迟加载队列有关。
  5. 返回查询的结果。

在这里插入图片描述

从数据库中查询获取数据

  1. 设置本地缓存,使用占位符占位。
  2. 执行查询的操作
  3. 删除占位符,将查询结果put到本地缓存中。
  4. 返回查询结果

在这里插入图片描述
主要查询在doQuery方法中。获取到对应的configuration对象,初始化创建一个statementHandler对象,使用该对象初始化jdbc中的prepareStatement对象并进行读操作。
在这里插入图片描述
初始化jdbc中的prepareStatement中,先获取连接对象connection后,使用statementHandler创建 Statement 或 PrepareStatement 对象
在这里插入图片描述
获取连接对象connection会从连接池中获取。
在这里插入图片描述

在这里插入图片描述
最后statementHandler执行查询后返回。
在这里插入图片描述

上述的Executor.query()⽅法⼏经转折,最后会创建⼀个StatementHandler对象,然后将必要的参数传递给StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。

从上⾯的代码中我们可以看出,Executor的功能和作⽤是

  1. 根据传递的参数,完成SQL语句的动态解析,⽣成BoundSql对象,供StatementHandler使⽤;
  2. 为查询创建缓存,以提⾼性能
  3. 创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果。
③ StatementHandler源码

StatementHandler主要完成两个工作:

  • 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包含若⼲个?占位符,我们其后再对占位符进⾏设值。StatementHandler通过parameterize(statement)⽅法对 Statement 进⾏设值;
  • StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)⽅法来完成执⾏Statement,和将Statement对象返回的resultSet封装成List;

进⼊到 StatementHandler 的 parameterize(statement)⽅法的实现:
在这里插入图片描述

进入parameterHandler.setParameters(),设置statement中的参数。
在这里插入图片描述
对某一个statement进行设置参数。根据我们输⼊的参数,对statement对象的?占位符处进⾏赋值
在这里插入图片描述

进⼊到StatementHandler 的 query(Statement statement, ResultHandler resultHandler)⽅法的实现
在这里插入图片描述

在这里插入图片描述ResultSetHandler 的 handleResultSets(Statement)⽅法会将 Statement 语句执⾏后⽣成的 resultSet结果集转换成List结果集

在这里插入图片描述

在这里插入图片描述

Mapper代理方式

1. 扫描mapper

要使用Mapper代理方式的方法调用。首先需要使用sqlSession.getMapper()方法将需要代理类传入,并返回生成的代理对象。
在这里插入图片描述
MyBatis是如何扫描mapper的,通过我们核心配置文件xml中的mappers标签进行读取、扫描,mappers标签中提供了两种方式,一种是mapper标签(注入单个mapper),一种是package标签(将包名下的所有mapper进行注入)

在这里插入图片描述
在源码中org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration解析方法中可以找到mappers标签的解析

在这里插入图片描述
首先会判断标签是package还是mapper,在根据标签获取mapper。我们以package标签为例,Configuration类中有个添加mapper映射的方法,通过对应的包名获取。

在这里插入图片描述
这里将需要扫描包中所有mapper接口添加到mapperRegistry注册器中。整个mapper扫描注册过程到这里就结束。
在这里插入图片描述

2. sqlSession.getMapper()解析

由上面的mapper扫描过程中,可以发现多了一个MapperRegistry类,主要包含了两个属性,一个是Configuration,一个是Map类,Map类中的key是我们传入的需要代理的mapper接口,value值根据词义来看是mapper代理工厂。
在这里插入图片描述
sqlSession.getMapper()方法出发,底层是有Configuration类提供的getMapper方法。除了传入需要代理类外,还传入了调用getMapper方法的sqlSession。

在这里插入图片描述
Configuration类中的getMapper方法实际上调用的是MapperRegistry类中的getMapper方法。
在这里插入图片描述
根据上文描述可以这里使用了mapper接口的字节码对象作为key获取对应的MapperProxyFactory对象,然后通过动态代理工厂生成实例。
在这里插入图片描述

动态代理工厂生成实例最终调用的是JDK中的Proxy生成的代理对象,所以可以表明MyBatis中是使用JDK动态代理对mapper接口产生代理对象。
在这里插入图片描述
代理对象是调用第三个参数InvocationHandler中的invoke方法。
在这里插入图片描述
进入该方法确实发现有个invoke方法,这里就是对需要代理的对象方法执行。
在这里插入图片描述
执行方法中除了select方法外,其他例如insert、update、delete方法都会发现执行了sqlSession提供的API。

在这里插入图片描述
其实select中执行的也是sqlSession提供的api,只是由于返回的结果不同,需要进行不同的操作。
在这里插入图片描述

总结:

MyBatis初始化时对接⼝的处理:MapperRegistry是Configuration中的⼀个属性,它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。

mappers中可以配置接⼝的包路径,或者某个具体的接⼝类。当解析mappers标签时,它会判断解析到的是mapper配置⽂件时,会再将对应配置⽂件中的增删改查标签封装成MappedStatement对象,存⼊mappedStatements中。

当判断解析到接⼝时,会建此接⼝对应的MapperProxyFactory对象,存⼊HashMap中,key =接⼝的字节码对象,value =此接⼝对应的MapperProxyFactory对象

在动态代理返回了实例后,我们就可以直接调⽤mapper类中的⽅法了,但代理对象调⽤⽅法,执⾏是在MapperProxy中的invoke⽅法中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值