mybatis源码解析(一)

Mybatis 源码解析 (一)

一、 ORM框架的作用

实际开发系统时,我们可通过JDBC完成多种数据库操作。这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下:

(1)注册数据库驱动类,明确指定数据库 URL地址、数据库用户名、密码等连接信息。
(2)通过 DriverManager 打开数据库连接。
(3)通过数据库连接创建 Statement 对象。
(4)通过 Statement 对象执行SQL 语句,得到 ResultSet 对象。
(5)通过 ResultSet 读取数据,并将数据转换成 JavaBean 对象。
(6)关闭 ResultSet 、Statement 对象以及数据库连接,释放相关资源。

按以上JDBC的方式,会出现

问题1:上述步骤(1)到步骤(4) 以及步骤(6) 在每次查询操作中都会出现,在保存、更新、删除等其他数据库操作中也有类似的重复性代码。

问题2:没有统一管理sql的地方

问题3:封装管理所有ResultSet 和 Statement 查询结果的关系映射也比较复杂

问题4:实际生产环境中对系统的性能是有一定要求的,数据库作为系统中比较珍贵的资源,极易成为整个系统的性能瓶颈。应用程序一般需要通过集成缓存、数据源、数据库连接池等组件进行优化

为了解决上面的问题, ORM ( Object Relational Mapping ,对象-关系映射)框架应运而生。如图所示, ORM 框架的主要功能就是根据映射配置文件,完成数据在对象模型与关系模型之间的映射,同时也屏蔽了上述重复的代码,只暴露简单的API 供开发人员使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wl1RQD52-1650002907943)(d:\user\01412349\Application Data\Typora\typora-user-images\image-20220110144857696.png)]

二、mybatis整体架构

MyBatis 的整体架构分为三层, 分别是基础支持层、核心处理层和接口层:

在这里插入图片描述

核心处理层

配置解析在MyBatis 初始化过程中,会加载mybatis-config.xml 配置文件、映射配置文件以及Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration 对象中。
之后,利用该Configuration 对象创建SqlSessionFactory 对象。
待MyBatis 初始化之后,开发人员可以通过初始化得到Sq!SessionFactory 创建SqlSession 对象并完成数据库操作。
SOL 解析与scripting 模块MyBatis 实现动态SQL 语句的功能,提供了多种动态SQL 语句对应的节点,例如,< where>节点、< if>节点、< foreach>节点等。通过这些节点的组合使用, 开发人员可以写出几乎满足所有需求的动态SQL 语句。
My Batis 中的scripting 模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL 语句。之后会处理SQL 语句中的占位符,绑定用户传入的实参。
SOL 执行SQL 语句的执行涉及多个组件,其中比较重要的是Executor 、StatementHandler 、ParameterHandler 和ResultSetHandler。
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler 完成。StatementHandler 首先通过ParameterHandler 完成SQL 语句的实参绑定;
然后通过java.sql.Statement 对象执行SQL 语句并得到结果集;
最后通过ResultSetHandler 完成结果集的映射,得到结果对象并返回。
插件通过添加用户自定义插件的方式对MyBatis 进行扩展。

接口层

核心是SqlSession 接口,该接口中定义了MyBatis 暴露给应用程序调用的API ,也就是上层应用与MyBatis 交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。

MyBatis 执行一条SQL 语句的大致过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xFCiZOg-1650002907945)(d:\user\01412349\Application Data\Typora\typora-user-images\image-20220110145246084.png)]

MyBatis的主要成员

  • Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中

  • SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能

  • Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护

  • StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等

  • ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型

  • ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合

  • TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换

  • MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装

  • SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

  • BoundSql 表示动态生成的SQL语句以及相应的参数信息

三、初始化配置

mybatis扩展性非常好,可以加入各种插件,许多开源组件也都是基于它进行拓展的,从其初始化配置起,工厂模式就用起来了,同时在解析各个标签时,功能解耦也做得非常好。

1.xml文件结构

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="local/application.properties"></properties>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!-- 控制全局缓存(二级缓存)-->
        <setting name="cacheEnabled" value="true"/>
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
        <!--<setting name="proxyFactory" value="CGLIB" />-->
        <!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
        <!--
                <setting name="localCacheScope" value="STATEMENT"/>
        -->
        <setting name="localCacheScope" value="SESSION"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
            <dataSource type="POOLED">
                <property name="driver" value="${spring.datasource.driver-class-name}"/>
                <property name="url" value="${spring.datasource.jdbc-url}"/>
                <property name="username" value="${spring.datasource.username}"/>
                <property name="password" value="${spring.datasource.password}"/>
            </dataSource>
        </environment>
    </environments>

    //根据数据源的不同执行不同的代码
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    
    <mappers>
        <mapper resource="xml/InterfaceBaseInfoExtMapper.xml"/>
        <mapper resource="xml/InterfaceBaseInfoMapper.xml"/>
    </mappers>

</configuration>

2.解析xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.elements.user.dao.dbMapper" >
    <resultMap id="BaseResultMap" type="com.sf.zjw.dao.domain.InterfaceBaseInfo">
		<id column="id" jdbcType="BIGINT" property="id" />
		<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
		<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
		<result column="del_flag" jdbcType="BIGINT" property="delFlag" />
		<result column="interface_name" jdbcType="VARCHAR" property="interfaceName" />
		<result column="emp_num" jdbcType="VARCHAR" property="empNum" />
		<result column="emp_name" jdbcType="VARCHAR" property="empName" />
		<result column="api_path" jdbcType="VARCHAR" property="apiPath" />
		<result column="app_api_path" jdbcType="VARCHAR" property="appApiPath" />
	</resultMap>
    
  <select id="SelectTime"   resultType="String" databaseId="mysql">
   SELECT  NOW() FROM dual 
  </select>

  <select id="SelectTime"   resultType="String" databaseId="oracle">
   SELECT  'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')  FROM dual 
  </select>

</mapper>

3.建造者模式

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

public class Computer {
    private String cpu;//必须
    private String ram;//必须
    private int usbCount;//可选
    private String keyboard;//可选
    private String display;//可选
}

//折叠构造函数模式
public class Computer {
     ...
    public Computer(String cpu, String ram) {
        this(cpu, ram, 0);
    }
    public Computer(String cpu, String ram, int usbCount) {
        this(cpu, ram, usbCount, "罗技键盘");
    }
    public Computer(String cpu, String ram, int usbCount, String keyboard) {
        this(cpu, ram, usbCount, keyboard, "三星显示器");
    }
    public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {
        this.cpu = cpu;
        this.ram = ram;
        this.usbCount = usbCount;
        this.keyboard = keyboard;
        this.display = display;
    }
}

//第二种:Javabean 模式
public class Computer {
        ...

    public String getCpu() {
        return cpu;
    }
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
    public String getRam() {
        return ram;
    }
    public void setRam(String ram) {
        this.ram = ram;
    }
    public int getUsbCount() {
        return usbCount;
    }
...
}

第一种主要是使用及阅读不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。

第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错。为什么会出错?你set元素1时需要有一些特殊操作,set2时又要依赖1的结果,你怎么让别人知道要先set哪个参数?

xml文件的配置明显就很多!

builder模式有4个角色。

  • Product: 最终要生成的对象,例如 Computer实例。

  • Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()

  • ConcreteBuilder: Builder的实现类。

  • Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

    在xml解析过程中build结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bS5whuW-1650002907946)(d:\user\01412349\Application Data\Typora\typora-user-images\image-20220109022503570.png)]

还少了个XMLScriptBuilder,解析动态sql的

四、查询

执行器

  • BaseExecutor

      • SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Sttement 对象。(可以是 Statement 或 PrepareStatement 对象)
      • ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用, 不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map内,供 下一次使用。(可以是 Statement 或 PrepareStatement 对象)
      • BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都 添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行
  • CachingExecutor 先从缓存中获取查询结果,存在就返回,不存在,再委托给 Executor delegate 去数据库 取,delegate 可以是上面任一的 SimpleExecutor、ReuseExecutor、BatchExecutor。

装饰者模式CachingExecutor。

private final Executor delegate; 
private final TransactionalCacheManager tcm = new TransactionalCacheManager(); //新功能

缓存

MyBatis系统中默认定义了两级缓存, 一级 缓存和 二级缓存。
– 1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启,一级缓存默认实现类org.apache.ibatis.cache.impl.PerpetualCache。
– 2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
– 3、为了提高扩展性。MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

CacheKey的构成

核心功能,可以动态更新Key的hash值。因为底层缓存数据是基于HashMap实现的
比较顺序:hashCode–>checksum–>count–>updateList,只要有一个不等则说明不是相同的Key

从查询的源码可以看出,mybatis在查询前,先看是否需要清除缓存,再通过CacheKey从缓存中查找是否有对应的key,有则返回对象,无则从数据库中查找,再看一级缓存的作用域范围,是STATEMENT则清除缓存,否则将结果集放到缓存中。

一级缓存演示& & 失效情况

同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
一级缓存失效的四种情况
– 1、不同的SqlSession对应不同的一级缓存
– 2、同一个SqlSession但是查询条件不同
– 3、同一个SqlSession两次查询期间执行了任何一次增删改操作
– 4、同一个SqlSession两次查询期间手动清空了缓存

– sql标签的flushCache属性查询默认flushCache=false;增删改insert|update|delete会修改数据,默认flushCache=true,会同时清空一级和二级缓存。
– sqlSession.clearCache():只是用来清除一级缓存,不会清除二级缓存。
– 当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有有select中的缓存将被clear。

为什么要二级缓存

这就要说到为什么要以namespace级别来缓存。

MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要开启二级缓存,开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9PEnrsu0-1650002907946)(d:\user\01412349\Application Data\Typora\typora-user-images\image-20220109174001049.png)]

最常见的二级缓存用法,就是事务!

可以看源码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值