深入了解Mybatis架构设计
架构设计

我们可以把Mybatis的功能架构分为三层:
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
Mybatis和数据库的交互有两种方式:
- 使用传统的Mybatis提供API
- 使用Mapper代理的方式
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。他主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来最为基础组件。为上层的数据处理层提供最基础的支撑。
Mybatis主要构件
| 构件 | 描述 |
|---|---|
| SqlSession | 作为Mybatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删查改功能 |
| Executor | Mybatis执行器,是Mybatis调度的核心,负责SQL语句的生成和查询缓存的维护 |
| StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换为List集合 |
| ParameterHandler | 负责对用户传递的参数转换为JDBC Statement所需要的参数 |
| ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换为List类型的集合 |
| TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
| MappedStatement | MappedStatement维护了一条<select、 update 、 delete 、insert >节点的封装 |
| SqlSource | 负责根据用户传递的parameterObject,动态的生成SQL语句,将信息封装到BoundSql对象中 |
| BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |

总体流程:
- 加载配置并初始化
配置来源于两个地方,一个是配置文件(conf.xml,mapper*.xml),一个是java代码中的注解,将配置文件内容封装到Configuration,将sql的配置信息加载成为一个mappedstatement对象,存储在内存中。
2. 接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入的参数
将请求传递给下层的请求处理层进行处理
- 处理操作请求
- 根据SQL的ID查找对应的MappedStatement对象
- 根据传入参数对象解析,得到最终要执行的SQL和执行传入参数
- 获取数据库连接,将最终SQL语句和参数给到数据库执行,并得到执行结果
- 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果
- 释放连接资源
- 返回处理结果
Mybatis缓存
Mybatis有一级缓存和二级缓存。Mybatis收到查询请求后首先会查询二级缓存,若二级缓存未命中,再去查询一级缓存,一级缓存没有,再查询数据库。
一级缓存
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());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//从localCache缓存里查数据,没有就去查数据库
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--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
这个localCache是BaseExecutor里面的一个属性
public abstract class BaseExecutor implements Executor {
protected PerpetualCache localCache;
PerpetualCache类
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
二级缓存
启用二级缓存步骤:
- 开启cacheEnabled(默认打开)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 需要在二级缓存的Mapper配置文件中加入
<cache></cache>
- 注意,二级缓存要想生效,必须要调用sqlSession.commit或close方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
注意Cache cache = ms.getCache();,这个cache是从MappedStatement中获取到的,由于MappedStatement存在全局配置中,可以多个CachingExecutor获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共用一个缓存实例,会导致脏读的存在。
那么mybatis是怎么解决脏读的呢?借用了上面的tcm这个变量,也就是TransactionalCacheManager类来解决的。
TransactionalCacheManager类维护了Cache,TransactionalCache的关系,真正的数据还是交由TransactionalCache处理的。
结构如图:

public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
接下来看一下TransactionalCache的代码
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
// 真正的缓存对象
private final Cache delegate;
private boolean clearOnCommit;
//在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
//在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
// issue #116
//获取缓存的时候从delegate里获取的
Object object = delegate.getObject(key);
if (object == null) {
//缓存未命中,将key存入entriesMissedInCache.
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public void putObject(Object key, Object object) {
//put的时候只是将数据库的数据放入到了entriesToAddOnCommit
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
//刷新未缓存的结果到delegate中去
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
我们存储二级缓存的时候是放入到TransactionalCache.entriesToAddOnCommit这个map中,但是每次查询的时候是从delegate查询的,所以这个二级缓存查询数据库后,缓存是没有立刻生效的。只有当执行了sqlSession的commit或close方法后,它会调用到tcm的commit,在调用到transactionlCache的commit,刷新缓存到delegate了。
总结:
- 二级缓存的设计上,大量运用了装饰器模式,如SynchronizedCache、LoggingCache。
- 二级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
- 二级缓存的实现由CachingExecutor和一个事务型预缓存TransactionlCache完成。
深入了解Mybatis架构设计的更多相关文章
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...
- 《深入了解mybatis原则》 MyBatis架构设计和案例研究
MyBatis这是现在很流行ORM框架,这是非常强大.事实上现却比較简单.优雅. 本文主要讲述MyBatis的架构设计思路,而且讨论MyBatis的几个核心部件.然后结合一个select查询实例.深入 ...
- 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
作者博客:http://blog.csdn.net/u010349169/article/category/2309433 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简 ...
- 《深入理解mybatis原理1》 MyBatis的架构设计以及实例分析
<深入理解mybatis原理> MyBatis的架构设计以及实例分析 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构 ...
- MyBatis的架构设计以及实例分析
MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个sel ...
- MyBatis的架构设计以及实例分析--转
原文地址:http://blog.csdn.net/luanlouis/article/details/40422941 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单 ...
- mybatis深入理解(四)-----MyBatis的架构设计以及实例分析
MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...
- Mybatis架构学习
Mybatis架构学习 MyBatis 是支持定制化 SQL.存储过程以及高级映射的持久层框架.MyBatis 封装了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.可以对配置和原生Map使用 ...
- MyBatis架构(转)
本文来自http://www.bubuko.com/infodetail-549184.html 如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处 ...
随机推荐
- DISCUZ论坛添加页头及页尾背景图片的几种方法
先给大家分享页头添加背景图片的两种方法:1. 第一种效果,是给discuz的整体框架添加背景图片,见图示: 添加方法如下:找到你现在使用模板common文件下的header.html文件,在<h ...
- (转载https://segmentfault.com/a/1190000016313947)了解RestFul Api架构风格设计
最近几年REST API越来越流行,特别是随着微服务的概念被广泛接受和应用,很多Web Service都使用了REST API. REST是HTTP规范主要编写者之一的Roy Fielding提出的, ...
- Shell系列(32)- 双分支if语句判断Apache服务是否启动
#!/bin/bash #截取httped进程,并把结果赋予变量test test=$(ps -aux | grep "httpd" | grep -v "grep&qu ...
- 低差异序列 (low-discrepancy sequences)之Halton序列均匀产生多维随机数的介绍与实现
Halton序列 在统计学中,Halton序列是用于生成空间中的点的序列,如Monte Carlo模拟的数值方法,虽然这些序列是确定性的,但它们的差异性很低,也就是说,在许多方面看起来是随机的.它们在 ...
- sonar-scanner命令参数分析
C:\Users\huang>sonar-scanner -h INFO: INFO: usage: sonar-scanner [options] INFO: INFO: Options: I ...
- 启动springboot出现错误 Caused by: java.net.BindException: Address already in use: bind
如果运行过程中出现端口被占用 抛出了这个异常 首先可以在cmd中调出命令窗口然后 执行命令 netstat -ano 可以查看所有活动的连接 找到你被占用的端口 可以看到我被占用的端口的进程是 4 ...
- 安装redis3.0.5
首先在官网下载redis-3.0.5.tar.gz 在某一个要安装redis的目录下输入命令 tar xzf redis-3.0.5.tar.gz 实现解压缩 进入解压缩后的redis目录 输入mak ...
- .NET 5 WPF 调用OCX 经验分享
在.Net 5.0 WPF中调用OCX步骤如下: 1,用工具先把ocx转换成AxInterop.EloamViewLib.dll和Interop.EloamViewLib.dll.(这里是我用到的oc ...
- NER为什么那么难
命名实体识别(Name Entity Recognition) 是自然语言处理中一个比较基础的问题.要解决的问题是,从unstructure的文本当中找到实体并归类.当然我这么定义已经有了一定的bia ...
- Java读取属性配置文件-properties
在项目开发中,我们难免将一些可变的参数放在程序以外,作为一个单独的文件,即配置文件,这样方便项目在不同的使用环境部署时.或者说需要不同时,可以通过简单配置这些程序以外的文件来修改程序里的变量. 常用的 ...