MyBatis
mybatis相比较传统jdbc
- 传统数据库配置信息存在硬编码,包括sql语句,设置参数,获取结果集需要手动封装结果集,较为繁琐。
- 需要频繁创建数据库连接。
解决方案:
- 配置文件解决硬编码,数据库连接使用连接池。
- 结果集可以使用反射。
解析数据库配置 mapper.xml中的sql
sqlsession在getMapper时 使用动态代理生成代理对象(其中内部类中包含invoke方法),代理对象在调用接口中的任意方法都会执行invoke方法.这样就可以吧创建数据库连接放在里面
为什么myabtais mapper中的namespace与 id 要与接口名与方法名相同?
因为在通过动态代理时只有根据方法才能获取到当前的类名已经方法名,而这两个组成成为了sql mapper的唯一标识,才知道执行哪个语句,说白了就是为了做区分. 在做抽象封装是达成的约定.
mybatis:
基于ORM的半自动、轻量级、持久层框架(对象关系映射)
为什么调用方法是可以直接返回对象.
从技术层面来说是因为用了反射,但实际上是因为做了约束 实体中的字段与数据库字段做了映射关系,利用这层关系,完成结果集封装.
为什么是半自动,全自动(hibernate).不用sql语句的话,没法优化.
轻量级: 占用资源少
底层还是jdbc的封装,规避了常见的问题
API
传统开发方式
- Resoures工具类加载配置文件SqlConfigMap
- SqlSessionFactoryBuilder解析创建SqlSessionFactory
SqlSessionFactory生产一个SqlSession
openSession() 默认开启一个事务,不会自动提交,需要手动提交
openSession(true)则默认自动提交
- SqlSession调用方法
代理开发方式
Mapper 接⼝开发需要遵循以下规范:
- Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同
- Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同
- Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
- Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
通过sqlsession.getMapper(dao.class).获取代理对象
Mybatis缓存
一级缓存
介绍
一级缓存是sqlsession级别的缓存,通过hashmap存储缓存对象,不同sqlsession
之间的缓存数据区不受影响,一级缓存默认开启
先去一级缓存中查询,如果没有就查询数据库,并进行一级缓存
cacheKey:
statementId,params,bonundsql,rowbounds(分页参数[两个])
组成如果配置文件中配置了Environment,则会添加Environment ID
value:查询结果
第二次查询直接命中
可以通过比较结果地址验证。缓存的是对象
如果中间有commit操作,会清空一级缓存。避免脏读。
除了
commit
也可以手动调用sqlsession.clearCache
或者close
方法手动刷新一级缓存。
源码
数据结构HashMap
,
二级缓存
⼆级缓存是基于mapper⽂件的namespace,多个sqlsession共享二级缓存区域.二级缓存需要手动开启.
多个sqlsession中如果发生commit操作会清空二级缓存.
二级缓存缓存的是数据并不是对象.
开启方式
xml开发
全局配置文件sqlMapConfig中
其次在UserMapper.xml⽂件中开启缓存
注解开发
使用@CacheNamespace
开启二级缓存
usecache
(默认为false)和flushcache
(默认为true)
xml:可以在单个statement中配置使用
注解: 添加@options
注解
源码:
底层数据结构还是hashmap
存在的问题
- 二级缓存是单服务器工作,无法实现共享所以无法实现分布式缓存.
解决方法
redis、memcached、ehcache
自定义二级缓存
mybatis提供了redis实现类,可以直接使用mybatis-redis
依赖
架构原理
三层结构
API接口层
提供给外部使⽤的接⼝ API,开发⼈员通过这些本地API来操纵数据库。接⼝层⼀接收 到 调⽤请求就会调⽤数据处理层来完成具体的数据处理
数据处理层
负责具体的SQL查找、SQL解析、SQL执⾏和执⾏结果映射处理等。它主要的⽬的是根 据调⽤的请求完成⼀次数据库操作
基础支撑
包括连接管理、事务管理、配置加载和缓存处理,这些都是共⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑
总体流程
加载配置初始化
配置来源于两个地⽅,⼀个是配置⽂件(主配置⽂件conf.xml,mapper⽂件*.xml),—个是java代码中的 注解,将主配置⽂件内容解析封装到Configuration,将sql的配置信息加载成为⼀个
mappedstatement
对象,存储在内存之中接收调⽤请求
为SQL的ID和传⼊参数对象
处理操作请求
为SQL的ID和传⼊参数对象
- 根据SQL的ID查找对应的MappedStatement对象。
- 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
- 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
- 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处 理 结果。
- 释放连接资源。
返回处理结果
将最终的处理结果返回。
源码分析
sqlsession 线程不安全的,使用后需要关闭close()
- 读取配置文件以及注解,读成输入流
- 解析配置文件封装configuration对象,创建defaultsqlsessionfactory
- openSession :创建了DefaultSqlsession实例对象并且设置了事务不自动提交.以及完成executor对象创建
- sqlsession.selectlist根据statementid从configuration中的map集合中获取到mappedstatement
二级缓存
先执行二级缓存然后一级缓存然后数据库
开启二级缓存后走的就是CachingExecutor
先查询二级缓存 如果没有的话会调用delegate(这里的delegate此时是BaseExecutor的实现类 simpleExecutor)去执行query 而执行query的时候会查询一级缓存,然后再查数据库 ,此时会保存一级缓存,然后保存二级缓存,但是在保存二级缓存的时候还没有真正的去存cache对象,而是放到一个hashmap集合中 (这个集合的key是缓存对象,value是事务缓存),存储的都是事务没提交的缓存集合。为什有事务因为二级缓存是从MappedStatement中获取,然后这玩意存在全局配置中,可以多个CachingExecutor取到,会出现线程安全问题.并且多个事务共用一个缓存实例会导致脏读.所以就有了针对缓存的事务管理器 tcm(TransactionalCacheManager).
但是在第一步查询二级缓存的时候是直接取的真正缓存对象,所以两次查询之间需要用commit来提交上面 没提交的事务的缓存以此保存缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List list = (List) tcm.getObject(cache, key);
if (list == null) {
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);
存储⼆级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,但是每 次查询的时候是直接从TransactionalCache.delegate中去查询的,所以这个⼆级缓存查询数据库后,设 置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate 会导致脏数据问题
总结
- ⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
- ⼆级缓存具有丰富的缓存策略。
- ⼆级缓存可由多个装饰器,与基础缓存组合⽽成
- ⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor和 ⼀个事务型预缓存TransactionalCache 完成。
延迟加载
原理动态代理 javasist实现
因为sqlsession查询返回的对象是个代理对象.在调用代理对象属性的getter方法时 会进入拦截器的invoke方法,如果需要延迟加载则会查询。
本文由博客一文多发平台 OpenWrite 发布!