本节内容:
1、mybatis的工作流程
2、mybatis的架构分层和模块划分
3、mybatis的缓存
4、阅读mybatis源码掌握底层工作原理和设计思想
通过第一节的应用,应该差不多了解mybatis的工作流程了
1、解析配置文件
解析配置文件,全局文件和映射文件,封装成一个Configuration对象
2、提供操作接口
通过SqlSession获取数据库连接,只提供应用接口,还不是真正的执行对象
要获得会话就需要一个会话工厂SqlSessionFactory
工厂里要包含所有配置信息,所以通过Builer创建工厂类
3、执行SQL操作
SqlSession 持有的Executor对象,封装jdbc的操作
接口层
主要是我们用的api
核心处理层
主要功能:
1、把接口中的参数解析、映射成数据库类型
2、解析xml中的sql语句,绑定参数,动态拼接sql等等
3、执行sql
4、处理结果集,映射java对象
插件也是属于核心层的
基础服务层
主要是通用功能:
数据源
缓存
日志
xml解析
反射
io
事务等
cache缓存
主要是为了提升数据库效率,缓解压力
有一级缓存、二级缓存,也支持集成第三方缓存
缓存体系结构
cache接口,默认实现类perpetualCache,是基础缓存
还有很多装饰器,实现cache接口,实现很多额外的功能
默认开启的,产生的条件;
同一个会话,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);
执行结果:
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);
一级缓存在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);
执行结果:
除了增删改,select 标签 配置 flushCache=“true” 一级缓存直接没效果
一级缓存的缺点:
不能垮会话,可能会造成读取的数据过时问题。脏数据
解决一级缓存不能共享session的问题
范围是namespace级别的,可以被多个sqlsesion共享
思考:二级缓存是在一级缓存之前工作吗?在哪里维护
在一级缓存之前,因为耳机缓存范围大,取不到二级缓存才取一级缓存
一级缓存是在BaseExecutor中维护,二级缓存是在BaseExecutor之前创建的,通过配置是否开启二级缓存
CachingExecutor 这个装饰器维护耳机缓存,如果开启了二级缓存,就创建这个对象包装Executor
1、mybatis-config.xml 中配置 默认是true
2、在具体的mapper.xml中配置cache标签,设置 缓存对象个数、回收策略、刷新时间、是否读写
配置了缓存之后,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);
二级缓存使用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 跟标签,
看下解析的根标签
可以对比下标签的配置,都做了什么
最后看下mapper的解析
看下如何解析mapper资源的
mapperParser.parse();
解析所有的mapper文件
解析所有的namespace,获取所有mapper接口的class对象
并且把 namespace和 MapperProxyFactory 关联起来
具体看下这几个方法:
configurationElement()
解析mapper.xml文件中所有的标签,如namespace、cache
并且创建了 XMLStatementBuilder,用来解析增删改查的标签
bindMapperForNamespace()
addMapper();为每一个Mapper接口,创建一个代理工厂,可以创建代理
还会通过创建MapperAnnotationBuilder 去解析接口上的注解 ,如:@Select
mapper解析完成之后,又调用另一个build(); 创建一个DefaultSqlSessionFactory 对象返回
总结:
第一步做了:
config.xml解析
mapper.xml解析
mapper接口中注解的解析
得到了一个Configuration对象,这里存放了所有的配置
返回一个DefaultSqlSessionFactory 对象,里面持有Configuration实例
入口肯定是
SqlSession session = sqlSessionFactory.openSession();
openSession() 默认自动提交为false;openSession(true),则表示自动提交
DefaultSqlSessionFactory 调用 openSessionFromDataSource();
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里可能有缓存,也可能有插件。
上面执行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接口的代理对象
终于到了喜闻乐见的curd环节了,入口
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 管理缓存
防止出现数据库没有这个数据,缓存中确有数据的情况,所以二级缓存必须和事务关联
事务提交时,缓存才正真写入。
以及缓存本身就是一个会话,一个事务,事务回滚,会话就结束了,缓存也清空了。