mybatis(二)体系结构和工作原理

体系结构和工作原理

  • 工作流程
  • 架构分层和模块划分
  • mybaitis缓存
    • 一级缓存
    • 二级缓存
    • 第三方缓存
  • 源码解读
    • 解析配置
    • 创建会话
    • 获取Mapper对象
    • 执行sql

本节内容:

1、mybatis的工作流程
2、mybatis的架构分层和模块划分
3、mybatis的缓存
4、阅读mybatis源码掌握底层工作原理和设计思想

工作流程

通过第一节的应用,应该差不多了解mybatis的工作流程了

1、解析配置文件
	解析配置文件,全局文件和映射文件,封装成一个Configuration对象
2、提供操作接口
	通过SqlSession获取数据库连接,只提供应用接口,还不是真正的执行对象
	要获得会话就需要一个会话工厂SqlSessionFactory
	工厂里要包含所有配置信息,所以通过Builer创建工厂类
3、执行SQL操作
	SqlSession 持有的Executor对象,封装jdbc的操作

流程图
mybatis(二)体系结构和工作原理_第1张图片

架构分层和模块划分

基于3.5.4 ,有21个包
mybatis(二)体系结构和工作原理_第2张图片
mybatis(二)体系结构和工作原理_第3张图片

根据功能:
mybatis(二)体系结构和工作原理_第4张图片

接口层

主要是我们用的api

核心处理层

主要功能:
	1、把接口中的参数解析、映射成数据库类型
	2、解析xml中的sql语句,绑定参数,动态拼接sql等等
	3、执行sql
	4、处理结果集,映射java对象

插件也是属于核心层的

基础服务层

	主要是通用功能:
		数据源
		缓存
		日志
		xml解析
		反射
		io
		事务等

mybaitis缓存

cache缓存

	主要是为了提升数据库效率,缓解压力
	有一级缓存、二级缓存,也支持集成第三方缓存

缓存体系结构

	cache接口,默认实现类perpetualCache,是基础缓存
	还有很多装饰器,实现cache接口,实现很多额外的功能

mybatis(二)体系结构和工作原理_第5张图片
mybatis(二)体系结构和工作原理_第6张图片

一级缓存

默认开启的,产生的条件;
	同一个会话,sqlSession
	同一个sql
上面说的基础缓存 perpetualcache,放在executor里维护,
同一个会话,同一个sql执行时,会直接去内存查询,不会去数据库
SqlSession session = sqlSessionFactory.openSession();
        
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.queryUser();
        System.out.println("第一次查询============="+user);
        
        UserMapper userMapper1 = session.getMapper(UserMapper.class);
        User user1 = userMapper1.queryUser();
        System.out.println("第二次查询============="+user1);

执行结果:

mybatis(二)体系结构和工作原理_第7张图片
不是一个会话

 SqlSession session1 = sqlSessionFactory.openSession();

        SqlSession session = sqlSessionFactory.openSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.queryUser();
        System.out.println("第一次查询============="+user);

        UserMapper userMapper1 = session1.getMapper(UserMapper.class);
        User user1 = userMapper1.queryUser();
        System.out.println("第二次查询============="+user1);

执行结果:
mybatis(二)体系结构和工作原理_第8张图片
可以看见操作了两次数据库

一级缓存在BaseExecutor的 query()  ---->queryFromDatabase()中存入,在queryFromDatabase()之前get();

一级缓存何时put,何时get,何时clear

在queryFromDatabase方法里,putObject和removeObject方法

一级缓存怎么命中的

BaseExecutor的 query()  里面,localCache.putObject(key...)

一级缓存何时清空

同一个会话做了增删改的操作
UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.queryUser();
        System.out.println("第一次查询============="+user);


        UserMapper updateMapper = session.getMapper(UserMapper.class);
        updateMapper.updateUser();
        session.commit();

        System.out.println("修改user============"+user);

        UserMapper userMapper1 = session.getMapper(UserMapper.class);
        User user1 = userMapper1.queryUser();
        System.out.println("第二次查询============="+user1);
执行结果:

mybatis(二)体系结构和工作原理_第9张图片
而且这里不管事务提不提交,都会清除一级缓存

除了增删改,select 标签 配置 flushCache=“true” 一级缓存直接没效果

一级缓存的缺点:

不能垮会话,可能会造成读取的数据过时问题。脏数据

二级缓存

解决一级缓存不能共享session的问题
范围是namespace级别的,可以被多个sqlsesion共享

思考:二级缓存是在一级缓存之前工作吗?在哪里维护

在一级缓存之前,因为耳机缓存范围大,取不到二级缓存才取一级缓存
一级缓存是在BaseExecutor中维护,二级缓存是在BaseExecutor之前创建的,通过配置是否开启二级缓存
CachingExecutor 这个装饰器维护耳机缓存,如果开启了二级缓存,就创建这个对象包装Executor

mybatis(二)体系结构和工作原理_第10张图片
开启二级缓存

1、mybatis-config.xml 中配置  默认是true
2、在具体的mapper.xml中配置cache标签,设置 缓存对象个数、回收策略、刷新时间、是否读写

mybatis(二)体系结构和工作原理_第11张图片

	配置了缓存之后,select会缓存,增删改会清楚缓存
	先拿二级缓存,拿不到拿一级缓存,最后查数据库

只配了开启二级缓存,但是不配cache,会有装饰器对象产生,但是不会走二级缓存

  SqlSession session = sqlSessionFactory.openSession();
        SqlSession session1 = sqlSessionFactory.openSession();

        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.queryUser();

        System.out.println("第一次查询============="+user);
		
		//必须提交,事务不提交,二级缓存不会写入
        session.commit();

        
        UserMapper userMapper1 = session1.getMapper(UserMapper.class);
        User user1 = userMapper1.queryUser();



        System.out.println("第二次查询============="+user1);

执行结果:
mybatis(二)体系结构和工作原理_第12张图片
为什么必须提交事务才会写入二级缓存

	二级缓存使用TransactionCacheManager 管理,最后又调用TransactionCache 的getObject、putObject和commit
方法TransactionCache 才有 了层层装饰的perpetualCache对象
	调用 putObject 时,只是把数据添加到了entriesToAddOnCommit里,只有调用了commit(),才会执行
flushPendingEnties 去写入缓存。它在DefaultSqlSession的commit时被调用

关闭清除缓存

flushcache = false   ,会造成数据脏读

何时开启二级缓存

一级缓存默认开启,二级缓存需要再mapper中配置cache标签
1、查询历史数据、历史订单时用,
2、多个namespace 共用同一个表,一个刷新缓存,一个没刷新,就会脏读
	推荐一个mapper里操作单表的情况使用
3、多个namespace 共享缓存 用 cache-ref  

第三方缓存

集成 eccache  或者redis

集成redis

<dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-redis</artifactId>
      <version>1.0.0-beta2</version>
    </dependency>

配置mapper.xml

<cache type="org.mybatis.caches.redis.RedisCache" eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

源码解读

还是从mybatis api入口
mybatis做了哪些事情

1、解析配置文件,创建工厂类
2、工厂类创建SqlSession
3、获得Mapper对象
4、调用接口方法,执行sql
5、关闭session

看源码注意事项

1、带着问题去看,猜想
2、学编程风格,设计思想
3、先抓重点,再看分支
4、记录核心流程和对象

解析配置

目的:
获取一个DefaultSqlSessionFacotory实例,这个实例里面有个Configuration对象,Configuration对象里包含了 全局配置和所有Mapper配置文件的所有信息。

	解析文件是:mybatis-config.xml和  *Mapper.xml
	先从Builder创建SqlSessionFactory开始

sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

建造者模式(封装复杂的创建过程)

build()方法有9个重载方法,分别以不同的方式创建SqlSessionFctory,默认是单例的

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

XMLConfigBuilder 是 BaseBuilder 子类,用来解析全局配置文件,还有一些其他子类,

XMLMapperBuilder:解析mapper映射器
XMLStatmentBuilder:解析增删改查标签
XMLScriptBuilder:解析动态SQL

在parse()方法中返回Configuration对象,实际上就是解析 全局配置文件的Configuration 跟标签,
看下解析的根标签
mybatis(二)体系结构和工作原理_第13张图片
可以对比下标签的配置,都做了什么
最后看下mapper的解析
mybatis(二)体系结构和工作原理_第14张图片
看下如何解析mapper资源的

mapperParser.parse();
解析所有的mapper文件
解析所有的namespace,获取所有mapper接口的class对象
并且把  namespace和  MapperProxyFactory 关联起来

具体看下这几个方法:
configurationElement()
解析mapper.xml文件中所有的标签,如namespace、cache
mybatis(二)体系结构和工作原理_第15张图片

并且创建了 XMLStatementBuilder,用来解析增删改查的标签

bindMapperForNamespace()

addMapper();为每一个Mapper接口,创建一个代理工厂,可以创建代理
还会通过创建MapperAnnotationBuilder 去解析接口上的注解  ,如:@Select 

mapper解析完成之后,又调用另一个build(); 创建一个DefaultSqlSessionFactory 对象返回

总结:

第一步做了:
config.xml解析
mapper.xml解析
mapper接口中注解的解析
得到了一个Configuration对象,这里存放了所有的配置
返回一个DefaultSqlSessionFactory  对象,里面持有Configuration实例

创建会话

入口肯定是

SqlSession session = sqlSessionFactory.openSession();
openSession() 默认自动提交为falseopenSession(true),则表示自动提交

DefaultSqlSessionFactory 调用 openSessionFromDataSource();

mybatis(二)体系结构和工作原理_第16张图片

1、利用environment标签,创建Transaction
	如果是jdbc,就用commit()、rollback()、close()管理,
	如果是MANAGED,就交给容器管理,
	如果是Spring集成的,默认交给spring
2、创建Executor
	几种Executor 都继承了BaseExecutor,BaseExecutor又实现了Executor接口,这是一种模板方法
	SimpleExecutor是默认的执行器类型
	在DefaultSqlSession这个类中,看到了调用的api 方法selectOne,可以看到所有的增删改查方法
	都是Executor的实现类里执行的
3、缓存装饰
	如果缓存开启了,cacheEnable = true 就会用装饰器去装饰 Executor
	executor = new CachingExecutor((Executor)executor);
4、给Executor 做插件代理
	Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
5、返回SqlSession 实现类的对象

总结:
在这一步创建一个sql会话,核心是对Executor的构建,sql都是executor执行的,Executor里可能有缓存,也可能有插件。

获取Mapper对象

上面执行sql的代码用了两种方式
1、User user = (User) session.selectOne(“com.test.mybatis.mapper.UserMapper.queryUser”,1);
2、UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.queryUser();
第一种方法是老的,现在都用第二个方法执行,所以入口是:

	UserMapper userMapper = session.getMapper(UserMapper.class);  可以看出里面用的泛型

getMapper方法

1、根据泛型,用class对象从configuration中获取Mapper接口对应的代理Mapper工厂类,
这个在上面解析Mapper配置文件时可以看到
2、利用jdk动态代理创建了Mapper接口的代理对象

mybatis的代理模式

代理模式我们都知道,三个核心对象 ,被代理类,接口,代理管理类(实现InvocationHandler)
这里Mapper接口没有实现类,因为我们只是需要通过mapper接口找到mapper.xml的statement_Id,
不需要实现类就能完成。

总结:

	获取Mapper其实是返回了一个Mapper接口的代理对象

执行sql

终于到了喜闻乐见的curd环节了,入口

mybatis(二)体系结构和工作原理_第17张图片
mybatis(二)体系结构和工作原理_第18张图片
User user = userMapper.queryUser();
userMapper是个代理类,所有的方法调用都会走invoke()方法
直接看 MapperProxy 的 invoke()方法
1、判断是否执行sql,还是直接执行方法
2、获取缓存
为了提升Mapper中方法的速度,加入了缓存
MapperMethod m = cachedMapperMethod(method),
这个MapperMethod 有俩属性
SqlCommand command ----封装statement_id 和sql
MethodSignature method-----封装返回值的类型
3、接下来调用MapperMethod 的execute()方法
这里根据上面包装的command里的statement类型,调用增删改查
1、现将参数转成数据库类型参数
2、执行sql
3、看下查询方法中的selectOne方法,一顿操作猛如虎返回了一个MappedStatement
ms里有执行sql的所有元素,id,statementType,sql,cache,入参,出参
执行query()的方法
CachingExecutor.query();
1、创建缓存key
2、查询另一个query方法
看看有没有缓存,缓存对象是之前解析mapper.xml的时候创建的
取出二级缓存 getObject…一直找到perpetualCache
如果查询不到就从数据库里读取数据
3、数据库读取数据
queryFromDatabase()
默认是SimpleExecutor 类
1、创建statementHandler
2。创建statement
3、执行prepareStatement的execute方法
4、利用 handleResultSets 处理结果集

头晕~~~~

同一个缓存查询的要素有哪些

	1、方法相同
	2、翻页偏移量相同
	3、SQL相同
	4、参数值相同
	5、数据源环境相同

TCM TransactionalCacheManager 管理缓存

防止出现数据库没有这个数据,缓存中确有数据的情况,所以二级缓存必须和事务关联
事务提交时,缓存才正真写入。
以及缓存本身就是一个会话,一个事务,事务回滚,会话就结束了,缓存也清空了。

Mybatis的5大核心对象
mybatis(二)体系结构和工作原理_第19张图片

你可能感兴趣的:(mybatis)