mybatis源码解析(代码片段)

柚几哥哥 柚几哥哥     2023-04-01     800

关键词:

文章目录

1、工作原理

1.1 初始化

1.1.1 系统启动的时候,加载解析全局配置文件和相应的映射文件

// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

全局配置文件:mybatis-config.xml

映射文件:mapper/*.xml

// 2.加载解析配置文件并获取 SqlSessionFactory 对象
// SqlSessionFactory 的实例我们没有通过 DefaulSqlSessionFactory 直接来获取
// 而是通过一个 Builder 对象来建造的
// SqlSessionFactory 生产 SqlSession 对象的 SqlSessionFactory 应该是单例
// 全局配置文件和映射文件 也只需要在 系统启动的时候完成加载操作
// 通过建造者模式来 构建复杂的对象: 1.完成配置文件的加载解析  2.完成 SqlSessionFactory 的创建
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

加载解析的相关信息存储在SqlSessionFactory对象的Configuration属性里,

1.1.2 建造者模式帮助我们解决复杂对象的创建:

  1. 完成配置文件的加载解析
  2. 完成 SqlSessionFactory 的创建

1.2 处理SQL请求的流程

通过工厂得到SqlSession对象

// 3.根据 SqlSessionFactory 对象获取 SqlSession 对象
SqlSession sqlSession = factory.openSession();

1.2.1 通过sqlSession中提供的API方法来操作数据库

// 4.通过sqlSession中提供的API方法来操作数据库
List<User> list = sqlSession.selectList("com.boge.mapper.UserMapper.selectUserList");

1.2.2 获取接口的代码对象-得到的其实是 通过JDBC代理模式获取的一个代理对象

// 获取接口的代码对象-得到的其实是 通过JDBC代理模式获取的一个代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

1.2.3 处理完请求之后,需要关闭会话SqlSession

//5.关闭会话
sqlSession.close();//关闭session  清空一级缓存

1.3 底层

全局配置文件的加载解析:Configuration

映射文件的加载解析:Configuration.mappedStatements

/**
     * MappedStatement 映射
     *
     * KEY:`$namespace.$id`
     */
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

生产了DefaultSqlsession实例对象,完成了Executor对象的创建,以及二级缓存CachingExecutor的装饰,同时完成了插件逻辑的植入。

selectOne():二级缓存->一级缓存->数据库插入

    // 1. 读取配置文件,读成字节输入流,注意:现在还没解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. 解析配置文件,封装Configuration对象   创建DefaultSqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 生产了DefaultSqlsession实例对象   设置了事务不自动提交  完成了executor对象的创建
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.(1)根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象
       //(2)将查询任务委派了executor执行器
    User user =  sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
    System.out.println(user);

    User user2 =  sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1);
    System.out.println(user2);

    // 5.释放资源
    sqlSession.close();

1.3.1 原理图:

1.3.2 源码结构:

2、MyBatis中的缓存

2.1 缓存的作用

降低数据源的访问频率,从而提高数据源的处理能力,提高服务器的响应速度。

2.2 缓存的设计

2.2.1 架构设计

通过装饰者模式对Cache接口的工作做增强

装饰者作用
BlockingCache阻塞的 Cache 实现类
FifoCache基于先进先出的淘汰机制的 Cache 实现类
LoggingCache支持打印日志的 Cache 实现类
LruCache基于最少使用的淘汰机制的 Cache 实现类
ScheduledCache定时清空整个容器的 Cache 实现类
SerializedCache支持序列化值的 Cache 实现类
SoftCache软引用缓存装饰器
SynchronizedCache同步的 Cache 实现类
TransactionalCache支持事务的 Cache 实现类,主要用于二级缓存中
WeakCache弱引用缓存装饰器

2.2.2 一级缓存和二级缓存

一级缓存:session级别,默认开启
<!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
<setting name="localCacheScope" value="STATEMENT"/>

二级缓存:SqlSessionFactory级别(工厂/进程级别)

开启二级缓存:
  • 在mybatis配置文件中配置cacheEnabled为true

    <!-- 控制全局二级缓存,默认ture-->
    <setting name="cacheEnabled" value="true"/>
    <!-- 延迟加载的全局开关。开启时,所有关联对象都会延迟加载。默认false -->
    <setting name="lazyLoadingEnabled" value="true"/>
    
  • 在映射文件中添加cache标签,可以在cache标签中更细致的增加配置

    <!--二级缓存开启-->
    <cache />
    
  • 命名空间下的所有标签放开二级缓存

  • 可以通过在标签中添加 useCache=false 指定api不走二级缓存

mybatis-config.xml

2.2.3 缓存的处理顺序

  • 获取mapper映射文件中cache标签里的配置MappedStatement.getCache()
  • 如果cache配置不为空,从二级缓存中查找(List) TransactionalCacheManager.getObject(cache, key);
  • 如果没有值,则执行查询, Executor.query()这个查询实际也是先走一级缓存查询,
  • 一级缓存也没有的话,则进行DB查询
  • 先将查询到的结果放入缓存TransactionalCacheManager.putObject(cache, key, list),再返回结果
2.2.3.1 先在二级缓存中查找

原因:找到概率更高,性能角度。

一级缓存二级缓存
作用域SqlSession级别SqlSessionFactory级别
找到概率5%90%
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException 

        // 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
        // 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
        // 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
        Cache cache = ms.getCache();

        // 如果配置文件中没有配置 <cache>,则 cache 为空
        if (cache != null) 
            //如果需要刷新缓存的话就刷新:flushCache="true"
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) 
                // 暂时忽略,存储过程相关
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                // 从二级缓存中,获取结果
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) 
                    // 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 缓存查询结果
                    tcm.putObject(cache, key, list); // issue #578 and #116
                
                // 如果存在,则直接返回结果
                return list;
            
        
        // 不使用缓存,则从数据库中查询(会查一级缓存)
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    
2.2.3.2 Executor.query()先走一级缓存查询,一级缓存也没有的话,则进行DB查询
    /**
     * 记录嵌套查询的层级
     */
    protected int queryStack;
    /**
     * 本地缓存,即一级缓存
     */
    protected PerpetualCache localCache;

    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException 
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        // 已经关闭,则抛出 ExecutorException 异常
        if (closed) 
            throw new ExecutorException("Executor was closed.");
        
        // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
        if (queryStack == 0 && ms.isFlushCacheRequired()) 
            clearLocalCache();
        
        List<E> list;
        try 
            // queryStack + 1
            queryStack++;
            // 从一级缓存中,获取查询结果
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // 获取到,则进行处理
            if (list != null) 
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            // 获得不到,则从数据库中查询
             else 
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            
         finally 
            // queryStack - 1
            queryStack--;
        
        if (queryStack == 0) 
            // 执行延迟加载
            for (DeferredLoad deferredLoad : deferredLoads) 
                deferredLoad.load();
            
            // issue #601
            // 清空 deferredLoads
            deferredLoads.clear();
            // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) 
                // issue #482
                clearLocalCache();
            
        
        return list;
    

3、如何扩展MyBatis中的缓存

3.1 架构理解

3.2 实际开发

/**
 * 永不过期的 Cache 实现类,基于 HashMap 实现类
 *
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache 
    /**
     * 缓存容器
     */
    private Map<Object, Object> cache = new HashMap<>();
        @Override
    public void putObject(Object key, Object value) 
        cache.put(key, value);
    

3.2.1 自定义三级缓存

创建Cache接口的实现,重写putObject和getObject方法

在mapper映射文件中的cache标签里增加type属性,关联自定义的Cache接口的实现

<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

如果未添加type,会默认读取 PERETUAL (二级缓存)

4、MyBatis中的涉及的设计模式

4.1 从整体架构设计分析

4.1.1 基础模块:

cache缓存模块:装饰器模式

Cache接口 定义了缓存的基本行为

PerpetualCache基于Cache实现,针对于缓存的功能 1.缓存数据淘汰;2.缓存数据的存放机制;3.缓存数据添加是否同步【阻塞】;4.缓存对象是否同步处理…做了增强处理–>代理模式

以及很多装饰类,灵活增强出适用于不同业务场景的Cache实现

logging日志模块:适配器模式、策略模式、代理模式

帮助我们适配不同的日志框架

Log接口针对不同日志框架,有不同的实现类,做增强处理

reflection反射模块:工程模式、装饰器模式
datasource数据源:工程模式
transaction事务模块:工厂模式
SqlSessionFactory:SqlSessionFactoryBuilder建造者模式

5、谈谈你对SqlSessionFactory的理解

  • 目的:创建SqlSession对象
  • 单例,在应用程序(服务)中只保存唯一的一份
  • SqlSessionFactory对象的创建是通过SqlSessionFactoryBuilder,
  • 同时也完成了全局配置文件Configuration和相关映射文件Mapper的加载和解析操作。
  • 涉及到了工厂模式和建造者模式
    /**
     * 构造 SqlSessionFactory 对象
     *
     * @param reader Reader 对象
     * @param environment 环境
     * @param properties Properties 变量
     * @return SqlSessionFactory 对象
     */
    @SuppressWarnings("Duplicates")
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) 
        try 
            // 创建 XMLConfigBuilder 对象
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            return build(parser.parse());
         catch (Exception e) 
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
         finally 
            ErrorContext.instance().reset();
            try 
                reader.close();
             catch (IOException e) 
                // Intentionally ignore. Prefer previous error.
            
        
    

    /**
    * 1.我们最初调用的build
    */
    public SqlSessionFactory build(InputStream inputStream) 
        //调用了重载方法
        return build(inputStream, null, null);
    

    /**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) 
        try 
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
         catch (Exception e) 
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        
    

6、谈谈你读SqlSession的理解

6.1 SqlSession

  • 作用:通过相关API来实现对应的数据的操作

  • SqlSession对象的获取需要通SqlSessionFactory来实现,

  • 作用域是会话级别,当一个新的会话到来的时候,需要新建一个SqlSession对象;当一个会话结束后,需要关闭相关会话资源

  • 处理请求的方式:

    1.通过相关crud的API直接处理

    2.通过getMapper(xx.xml)来获取相关mapper接口的代理对象来处理

6.2 SqlSession的安全问题

6.2.1 非线程安全:

6.2.2 Spring中是如何解决DefaultSqlSession的数据安全问题?

  • DefaultSqlSession是非线程安全的,也就意味着我们不能把DefaultSqlSession声明在成员变量中。
  • 每个线程都应该有自己的SqlSession实例。
  • 最佳作用域是请求或方法作用域
  • 决不能将SqlSession实例引用放在一个类的静态域,甚至一个类的实例变量也不行。
  • 应该将SqlSession放在一个和HTTP请求相似的作用域中,每次请求打开一个SqlSession,返回一个响应后就关闭他,关闭操作放在finally块中。

  • Spring中提供了SqlSessionTemplate来实现SqlSession的相关定义。
  • 其中每一个方法都通过SqlSessionProxy来操作,这是一个动态代理对象。
  • 在动态代理对象中通过方法级别的DefaultSqlSession来实现相关的数据库操作。

7、谈谈你对MyBatis的理解

  • 使用频率最高的ORM框架、持久层框架
  • 提供了非常方便的API实现CRUD
  • 支持灵活的缓存处理方案,一级缓存、二级缓存、三级缓存
  • 支持相关的延迟数据加载处理
  • 还提供了非常多的灵活标签,来实现复杂的业务处理,if forech where trim set bind…
  • 相比Hibernate(全自动化)会更加灵活

8、谈谈MyBatis中分页的理解

8.1 谈谈分页的理解:

  • 数据库层面SQL:

    MySQL:LIMIT

    Oracle:rowid

8.2 分页的实现

8.2.1 逻辑分页:RowBounds

8.2.2 物理分页:拦截器实现,执行分页语句的组装

9、谈谈MyBatis中的插件原理

9.1 插件设计的目的:

方便开发人员实现对MyBatis功能的增强

设计中MyBatis允许映射语句执行过程中的某一点进行拦截调用,允许使用插件拦截的方法包括:

9.2 实现原理:

9.2.1 创建自定义Java类,通过@Interceptor注解来定义相关的方法签名

9.2.2 在对应的配置文件中通过plugin来注册自定义的拦截器

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

9.2.3 拦截器的作用

  • 检查执行的SQL
  • 对执行SQL的参数做处理
  • 对查询的结果做装饰处理
  • 对查询SQL做分表处理

10、不同Mapper中的id是否可以相同?

可以相同,每一个映射文件的namespace都会设置为对应的mapper接口的全类路径名称

保证了每个Mapper映射文件的namespace是唯一的。

11、谈谈对MyBatis架构设计的理解

11.1 接口层

面向开发者,提供相关API

11.2 核心层

核心功能的实现:增删改查操作

11.3 基础模块

支撑核心层来完成核心的功能

查看详情

mybatis插件机制源码解析(代码片段)

引言本篇源码解析基于mybatis3.5.8版本。首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。mybatis的插件机制,让其扩展能力大大增加。比如我们项目... 查看详情

mybatis——源码解析mybatis框架底层的执行原理(代码片段)

文章目录:1.前言2.案例项目源码3.MyBatis源码解析底层执行原理3.1读取mybatis配置文件创建出SqlSeesionFactory对象3.2通过SqlSeesionFactory对象进而创建出SqlSession对象3.3通过SqlSession的getMapper获取到接口代理对象3.4通过mapper接口的代理... 查看详情

mybatis——源码解析mybatis框架底层的执行原理(代码片段)

文章目录:1.前言2.案例项目源码3.MyBatis源码解析底层执行原理3.1读取mybatis配置文件创建出SqlSeesionFactory对象3.2通过SqlSeesionFactory对象进而创建出SqlSession对象3.3通过SqlSession的getMapper获取到接口代理对象3.4通过mapper接口的代理... 查看详情

mybatis源码:mybatis配置解析(代码片段)

  Mybatis有两个核心配置,全局配置会影响Mybatis的执行;Mapper配置定义了查询的SQL,下面我们来看看Mybatis是如何加载配置文件的。  本文基于Mybatis源码(一):源码编译准备中案例进行分析,主要示例代码如下:publicstaticvoidma... 查看详情

源码解析mybatis架构(代码片段)

MySQL安装与启动安装并启动一个关系型数据是调试MyBatis源码的基础。目前很多互联网公司都将MySQL作为首选数据库,所以这里我也就选用MySQL数据库来配合调试MyBatis源码。1.下载MySQL首先,从MySQL官网下载最新版本的MySQLComm... 查看详情

#mybatis源码解析之配置加载(代码片段)

Mybatis源码解析之配置加载(二)这一篇是承接上一篇文章Mybatis源码解析之配置加载(一),上一篇原本是想把整个配置加载都分析完全,然后发现内容还是比较多,所以决定分成两篇来说好了,现在就开始剩下的配置分析... 查看详情

mybatis源码解析mybatis解析全局配置文件(代码片段)

MyBatis介绍MyBatis是一个持久层的ORM框架,使用简单,学习成本较低。可以执行自己手写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要... 查看详情

mybatis源码解析之配置加载(代码片段)

Mybatis源码解析之配置加载(一)用了好几年的mybatis了,但是很少来钻研mybatis原理所在,最近抽出空来,就把这一整套源码都研究了下,然后发现就是这些东西,mybatis没啥难度,于是决定把研究的这一整套写... 查看详情

mapperscannerconfigurer源码解析(代码片段)

声明:源码基于mybatis-spring1.3.2前文首先在阅读本文前需要明白整合后的使用方式以及熟悉MyBatis本身的工作原理,再者如果对于本文相关知识点不熟悉的可以参考下述文章。MyBatis与Spring整合SqlSessionTemplate源码解析Spring包扫描机制... 查看详情

mybaties源码解析(代码片段)

...分也花了几个小时的时间去琢磨但是感觉值了,我觉得对mybaties的原理更加清晰了     学了mybaties 查看详情

mybatis源码-sqlsession门面模式&selectlist源码解析(代码片段)

文章目录Pre工程概览pom.xmlmybatis-config.xmlUserMapper测试类selectList源码解析附SQLlog4j.propertiesapp.propertiesUserPre如果MyBatis的基础用法还不熟悉,31篇入门博客拿走不谢戳戳戳—>https://blog.csdn.net/yangshangwei/category_7205317 查看详情

mybatis源码-解析配置文件之配置文件configuration解析(超详细,值得收藏)(代码片段)

1.简介1.1系列内容本系列文章讲解的是mybatis解析配置文件内部的逻辑,即Readerreader=Resources.getResourceAsReader("mybatis-config.xml");SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(reader);其背后的逻辑。1.2适合对象了解 查看详情

mybatis源码分析四xml解析与核心对象的构建(代码片段)

四、XML解析与核心对象的构建到此为止,已经把MyBatis核心的代理以及与JDBC的交互逻辑梳理完成,下面来看看,配置文件以及mapper.xml的加载和SqlSessionFactory的创建。InputStreaminputStream=Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");... 查看详情

mybatis源码解析9---执行器executor解析(代码片段)

...ate去执行的。本文就分析下sql的执行器-----ExecutorExecutor是mybatis的sql执行器,SqlSession是面向程序的,而Executor则就是面向数据库的,先看下Executor接口的方法有 查看详情

mybatis的缓存机制源码分析之一级缓存解析(代码片段)

文章目录引言正文引言本篇源码解析基于mybatis3.5.8版本。MyBatis中的缓存指的是MyBatis在执行一次SQL查询时,在满足一定的条件下,会把这个sql和对应的查询结果缓存起来。当再次执行相同SQL语句的时候,就会直接从缓... 查看详情

mybatis的缓存机制源码分析之二级缓存解析(代码片段)

引言本篇源码解析基于mybatis3.5.8版本。MyBatis中的缓存指的是MyBatis在执行一次SQL查询时,在满足一定的条件下,会把这个sql和对应的查询结果缓存起来。当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,... 查看详情

mybatis——源码解析mybatis框架底层的执行原理(代码片段)

文章目录:1.前言2.案例项目源码3.MyBatis源码解析底层执行原理3.1读取mybatis配置文件创建出SqlSeesionFactory对象3.2通过SqlSeesionFactory对象进而创建出SqlSession对象3.3通过SqlSession的getMapper获取到接口代理对象3.4通过mapper接口的代理... 查看详情