avatar

mybatis源码刨析

Mybatis架构原理

架构设计

我们把Mybatis的功能架构分为三层:

  • Api接口层:提供给外部使用的接口api,开发人员通过这些本地API来操作数据库,接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。Mybatis和数据库交互有两种方式:
    • 使用传统的Mybatis提供的API
    • 使用Mapper代理的方式
  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求万册灰姑娘一次数据库操作
  • 基础支撑层:负责最基础的功能支撑,包括连接管理,事务管理,配置加载和缓存处理,这些都是公用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层一共最基础的支撑

主要构件及其互相关系

构件 描述
SqlSession 作为Mybatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor Mybatis执行器,是Mybatis调度核心,负责SQL语句的生成与查询缓存的维护
SatemwntHandler 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换为List集合
ParamenterHandler 负责对用户传递的参数转换为JDBC Statement所需的参数
ResultSetHandler 负责将JDBC 返回的ResultSet结果集对象转换为List类型的集合
TypeHandler 负责java数据类型与jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装
SqlSource 负责根据用户传递的paramenterObject,动态的生成sql语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息

总体流程

  • 1、加载配置文件并初始化
    • 触发条件:加载配置文件
    • 配置来源于两个地方,一个是配置文件(主配置文件config.xml,mapper文件*.xml),一个是java代码中的注解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个个mappedstatement对象,存储在内存之中
  • 2、接收调用请求
    • 触发条件:调用mybatis提供的api
    • 传入参数:为sql的ID和传入的参数对象
    • 处理过程:将请求传递到下层的请求处理层进行处理
  • 3、处理操作请求
    • 触发条件:API接口层传递请求过来
    • 传入参数:为SQL的ID和传入参数对象
    • 处理过程:
      • (1) 根据SQL的ID查找对应的MappedStatement对象
      • (2) 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数
      • (3) 获取到数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果
      • (4) 根据MappedStatement对象中的结果映射配置得到的执行结果进行转换处理,并得到最终的处理结果
      • (5) 释放连接资源
  • 4、返回处理结果

Mybatis源码剖析

传统方式源码刨析

Mapper方式源码刨析

二级缓存源码刨析

延迟加载源码刨析

什么是延迟加载

在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息,此时就是我们所说的延迟加载。就是在需要数据的时候才进行数据加载,不需要用到数据时就不加载数据。延迟加载也被称为懒加载。

  • 优点
    • 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快
  • 缺点
    • 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
  • 在多表中:
    • 一对多,多对多:通常情况下采用延迟加载
    • 一对一(多对一): 通常情况下次采用立即加载
  • 注意:
    • 延迟加载是基于嵌套查询来实现的

实现

首先我们创建两个实体类,一个用户,一个订单。其中属于一对多的关系。然后我们为其创建两个Mapper,我们在UserMapper中编写findById方法,其中的resultMap的collection节点是嵌套查询,select属性需要编写查询order对象的方法,column是查询方法的入参。

<mapper>
<!--创建一个resultMap 其中重点就是在collection中需要指定select与column,需要进行嵌套查询-->
<resultMap id="useMap" type="com.zenshin.mybatisMultitable.pojo.User">
<id property="id" column="id" />
<result property="username" column="username"/>
<!--添加select与column 进行嵌套查询-->
<!--select需要定位到OrderMapper中查询的sql语句,column是查询的输入参数-->
<collection property="orderList" ofType="com.zenshin.mybatisMultitable.pojo.Order" select="com.zenshin.mybatisMultitable.mapper.IOrderMapper.findOrderByUid" column="id">
<id property="id" column="oid" />
<result property="total" column="total"/>
<result property="orderTime" column="orderTime"/>
</collection>
</resultMap>
<select id="findById" resultMap="useMap" >
select * from user where id = #{id}
</select>
</mapper>

然后我们在OrderMapper中创建方法findOrderByUid

<mapper>
<!--这里需要跟UserMapper中的select属性中的信息保持一致-->
<select id="findOrderByUid" resultType="com.zenshin.mybatisMultitable.pojo.Order">
select * from orders where uid = #{uid}
</select>
</mapper>

这样编写好以后,我们编写测试类,进行调用测试

@Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
User user = sqlSession.selectOne("com.zenshin.mybatisMultitable.mapper.IUserMapper.findById","1");
System.out.println(user.getUsername());
}

这样一次查询就可以将所有的我们想要的数据查询出来,暂时还看不到延迟加载的效果,因为现在实现的效果是立即加载。

局部延迟加载

associationcollection标签中都有一个fetchType属性,这个属性控制着是否进行懒加载lazy 代表懒加载策略,eager属于立即加载策略

<resultMap id="useMap" type="com.zenshin.mybatisMultitable.pojo.User">
<id property="id" column="id" />
<result property="username" column="username"/>
<!--fetchType="lazy" 懒加载策略
fetchType="eager" 立即加载策略-->
<collection property="orderList" ofType="com.zenshin.mybatisMultitable.pojo.Order"
select="com.zenshin.mybatisMultitable.mapper.IOrderMapper.findOrderByUid" column="id" fetchType="lazy" >
<id property="id" column="oid" />
<result property="total" column="total"/>
<result property="orderTime" column="orderTime"/>
</collection>
</resultMap>

全局延迟加载

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略

<settings>
<!--开始全局懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
  • 注意:局部的加载策略优先级高于全局的加载策略

延迟加载原理

它的原理是,使用CGLIB或者javassist(默认)创建目标对象的代理对象。当调用代理对象的延迟加载属性的getting方法时,进入拦截器,比如调用user.getOrder().getName()方法,进入拦截器的invoke()方法,发现user.getOrder()需要延迟加载时,那么就会单独发送事先保存好的查询关联Order对象的SQL,把Order查询出来,然后调用user.setOrder(order)方法,于是user对象order属性就有值了,接着完成user.getOrder().getName()方法的调用,这就是延迟加载的基本原理
总结:延迟加载主要时通过动态代理的形式实现的,通过代理拦截到指定方法,执行数据加载。

//此时,user对象是一个代理对象,而非真正的user实体类
User user = sqlSession.selectOne("com.zenshin.mybatisMultitable.mapper.IUserMapper.findById","1");
文章作者: zenshin
文章链接: https://zlh.giserhub.com/2021/09/01/cl35o0mr2003jp4tgdc5lcrl1/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论