深入了解Mybatis架构设计

【12.MyBatis源码剖析与架构实战】1. MyBatis架构设计 MyBatis采用三层架构设计:接口层接收请求(支持Statement ID和Mapper接口两种调用方式),数据处理层是核心引擎(包含SQL解析、参数映射、SQL执行和结果处理四大组件),框架支撑层提供基础服务(事务管理、连接池、缓存等)。整个架构层次清晰,组件职责单一,通过动态代理和拦截器机制实现高度灵活性。查询请求依次经过各层组件处理,最终完成ORM映射并返回结果。这种分层设计使得MyBatis在保持强大功能的同时具备良好的扩展性和可维护性。 阅读详情

架构设计

我们可以把Mybatis的功能架构分为三层:

  1. API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

Mybatis和数据库的交互有两种方式:

  • 使用传统的Mybatis提供API
  • 使用Mapper代理的方式
  1. 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。他主要的目的是根据调用的请求完成一次数据库操作。
  2. 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来最为基础组件。为上层的数据处理层提供最基础的支撑。

Mybatis主要构件

构件描述
SqlSession作为Mybatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删查改功能
ExecutorMybatis执行器,是Mybatis调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换为List集合
ParameterHandler负责对用户传递的参数转换为JDBC Statement所需要的参数
ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换为List类型的集合
TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatementMappedStatement维护了一条<select、 update 、 delete 、insert >节点的封装
SqlSource负责根据用户传递的parameterObject,动态的生成SQL语句,将信息封装到BoundSql对象中
BoundSql表示动态生成的SQL语句以及相应的参数信息

总体流程:

  1. 加载配置并初始化

配置来源于两个地方,一个是配置文件(conf.xml,mapper*.xml),一个是java代码中的注解,将配置文件内容封装到Configuration,将sql的配置信息加载成为一个mappedstatement对象,存储在内存中。
2. 接收调用请求

触发条件:调用Mybatis提供的API

传入参数:为SQL的ID和传入的参数

将请求传递给下层的请求处理层进行处理

  1. 处理操作请求
  • 根据SQL的ID查找对应的MappedStatement对象
  • 根据传入参数对象解析,得到最终要执行的SQL和执行传入参数
  • 获取数据库连接,将最终SQL语句和参数给到数据库执行,并得到执行结果
  • 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果
  • 释放连接资源
  1. 返回处理结果

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);
  }

二级缓存

启用二级缓存步骤:

  1. 开启cacheEnabled(默认打开)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
  1. 需要在二级缓存的Mapper配置文件中加入
<cache></cache>
  1. 注意,二级缓存要想生效,必须要调用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完成。

书山有路勤为径,学海无涯苦作舟

基于FPGA低频方波测量-频率与占空比 阅读详情

相关推荐

Gartner DSG数据安全治理 架构及解读

目录背景:架构图: 解释:英文解释:(自上而下)平衡业务与风险、威胁、合规之间的关系(5个维度之间的平衡)优先处理数据集制定安全策略实施安全工具策略配置同步从上到下,从需求调研开始实施数据安全治理。千万不要跨过数据摸底、治理优先级分析、制定治理整体策略,而直接从技术工具开始对数据安全进行治理。 先明确选择需要治理的对象,优先选择重要数据进行数据安全治理工作。可以通过数据梳理、分类分级等手段,完成数据资产盘点和筛选,在纷杂的数据中理清头绪。

锦慈的技术博客 7215

MyBatis架构原理

一、架构整体设计 功能架构讲解: 我们把Mybatis的功能架构分为三层: (1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。 (2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。 (3)...

sinat_34814635的博客 3092

大一Python之循环解决实际问题

pythin之循环解决实际问题

weixin_66263546的博客 2287

【Mybatis】Mybatis架构简介

它会根据运行时用户传入的实参,解析动态 SQL 中的标签,并形成 SQL 模板,然后处理 SQL 模板中的占位符,用运行时的实参填充占位符,得到数据库真正可执行的 SQL 语句。持久层框架核心组件之一就是数据源,MyBatis 自身提供了一套不错的数据源实现,也是 MyBatis 的默认实现。MyBatis 的反射工具箱是在 Java 反射的基础之上进行的一层封装,为上层使用方提供更加灵活、方便的 API 接口,同时缓存 Java 的原生反射相关的元数据,提升了反射代码执行的效率,优化了反射操作的性能。

DreamSun的博客 3416

ANSYS_APDL实战入门——从零开始掌握模态分析

本文为ANSYS APDL模态分析实战入门指南,通过一个悬臂梁案例,手把手教你从零开始掌握APDL命令流。内容涵盖从模型创建、材料定义、网格划分到边界条件设置、求解及后处理结果查看的全流程,帮助工程师快速上手结构动力学分析,避免共振风险。

egg99的博客 1128

保姆级教程:用YOLOv8n模型在RKNN和Horizon平台完成目标检测(附C++部署代码与性能实测)

本文提供了一份详尽的YOLOv8n模型在RKNN和Horizon平台上的部署指南,涵盖环境配置、模型优化、跨平台适配及性能调优。通过实战案例和C++部署代码,帮助开发者在嵌入式设备上高效实现目标检测,显著提升推理性能。

weixin_42659194的博客 468

Mybatis框架详解(全)

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。

swy2560666141的博客 8466

【机电杂谈2】BLDC和PMSM的特点和区别

在日常生活中,小到电动玩具,大到电动汽车,永磁电机的身影可以说是无处不在。有刷直流电机、无刷直流电机、永磁同步电机由于其结构和输出特定的不同也被应用到不同的场合。如果你经常拆一些玩具和家电,就会看到大量的有刷直流电机和无刷直流电机,而永磁同步电机主要应用在高精度的工业伺服和电动汽车领域。本文就从电机的结构和控制方面,对这几种常见的永磁电机做简单的梳理和归纳。

2934

-MyBatis 源码环境搭建及整体架构解析

在上次,我通过一个订单系统的示例,展示了 MyBatis 在实践项目中的基本使用,以帮助你快速上手使用 MyBatis 框架。在这次,我就来带你搭建 MyBatis 源码调试的环境,并为你解析 MyBatis 的源码结构,这些都是在为后面的源码分析做铺垫。 MySQL 安装与启动 安装并启动一个关系型数据是调试 MyBatis 源码的基础。目前很多互联网公司都将 MySQL 作为首选数据库,所以这里我也就选用 MySQL 数据库来配合调试 MyBatis 源码 下载 MySQL 首先,从 MySQL 官网

Ssssongsmith 天璇星 537

安卓7.0+抓包难题:雷电模拟器9.0+Charles系统证书安装全攻略

HTTPS协议通过SSL/TLS加密保障网络通信安全,其核心机制依赖于证书信任链。在Android系统中,自7.0版本起,谷歌收紧了证书信任策略,默认仅信任系统预置的CA证书,这直接导致了传统抓包方法失效。理解这一原理对于移动开发、测试及安全研究至关重要,它揭示了应用层安全策略与系统底层机制的关联。通过将用户证书安装至系统信任区,可以突破此限制,实现HTTPS流量的解密与分析。这一技术在App调试、接口测试、安全审计等场景中具有广泛的应用价值。本文以雷电模拟器9.0和Charles抓包工具为例,详细演示了如

weixin_42521843的博客 279

2026年医疗AI的可信革命全栈实现(上)

医疗AI可信化:贝叶斯网络与向量数据库的协同架构 本文探讨2026年医疗AI发展的关键转折点——从追求生成能力转向构建可信决策系统。核心观点认为,医疗AI下一阶段的竞争焦点在于不确定性量化和错误传播约束,而非单纯的问答流畅度。研究提出以Python为开发语言、结合向量数据库与贝叶斯网络的混合架构: 技术分层 语言层(LLM):处理自然语言交互与工具调用 证据层(Milvus/Qdrant):实现可审计的临床知识检索 概率层(pgmpy/PyMC):执行贝叶斯推理与风险路由 治理层:确保权限控制与人工复核机制

AllenLV的博客 1149

《深入了解mybatis原则》 MyBatis架构设计和案例研究

MyBatis这是现在很流行ORM框架,这是非常强大。事实上现却比較简单、优雅。本文主要讲述MyBatis的架构设计思路,而且讨论MyBatis的几个核心部件。然后结合一个select查询实例。深入代码,来探究MyBatis的实现。 一、MyBatis的框架设计 注:上图非常大程度上參考了iteye 上的chenjc_it 所写...

aofan9566的博客 203

华为od 面试八股文_C++_01_含答案

华为od 面试八股文_C++_01_含答案

MISAYAONE的博客 5588

深入了解Mybatis架构设计,java最新面试题答案解析

这个cache是从MappedStatement中获取到的,由于MappedStatement存在全局配置中,可以多个CachingExecutor获取到,这样就会出现线程安全问题。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。// 真正的缓存对象。

2401_84023787的博客 823

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

MyBatis架构设计及源代码分析系列(一):MyBatis架构

MyBatis架构设计及源代码分析系列(一):MyBatis架构 如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处。 一、概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己The MyBatis data mapper framework makes it easier...

maxchenBug的博客 119

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

mybatis底层原理_mybatis

1.工作原理原型图:SqlSessionFactory构建方式:SqlSessionFactory构建 展开源码MapperSQL映射:XML配置 展开源码Java 注解配置 展开源码2.功能架构设计:Mybatis的功能架构可以分为三层:(1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。(2)数据...

weixin_39969060的博客 417

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

mybatis 丢失最后一个属性为空_MyBatis 延迟加载、一二级缓存、架构设计的面试题(常问,重点了解)...

MyBatis 的面试题整理快接近尾声了,我对着问题写了 6 个 Demo,把 MyBatis 的源码翻了几遍,收获满满。不知道你看的有没有收获?可以在文末参与讨论。Let's go on...1、MyBatis 的 SQL 执行日志如何开启?这是之前遗留的一个问题。在配置文件的标签上设置 logImpl 参数值(SLF4J | LOG4J | LOG4J2 | JDK_LOGG...

weixin_39984661的博客 128

最近重新看了一遍《Mybatis技术内幕》这本书,说一下我的心得

今天抽时间看了看大伙的消息,发现又有个小老弟要我推荐Mybatis的学习书籍,好家伙,我感觉我又找到水文的借口了,马上安排。其实关于MyBatis的话,官方文档已经做得足够好了,基本的使用查看官网就够了。而MyBatis源码解析的书不多,写得比较好的也就只有​ 《Mybatis技术内幕》 ​了好了,话不多说,一起来看看这本书。

m0_57042151的博客 223

Mybatis——MyBatis架构设计分析(一)

本文主要分析了MyBatis的架构设计。MyBatis采用三层架构,包括基础支撑层、核心处理层和接口层。接口层主要包含SqlSession等常用接口;核心处理层负责MyBatis初始化及SQL执行全流程,涉及配置解析、SQL解析执行等;基础支撑层提供资源加载、事务管理等基础功能。文章还提及了MyBatis的反射设计等内容。

庄小焱 1760

了解mybatis

什么是mybatis: Mybatis本质是一种半自动化的ORM框架,前身是ibatis,除了要pojo和映射关系之外,还需要些sql语句。   怎么看待ORM框架: 处理矛盾的,java程序员喜欢面向对象开发和面向接口开发,而数据库是一张张表和一个个库组成的,这两者的关系很难调和,而关系型映射解决了这一个问题。   Mybatis映射文件三要素: SQL; 映射规则; Pojo...

qq_41665481的博客 7385

MyBatis总体流程与架构设计详解

MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例,深入代码,来探究MyBatis的实现。

深入Java世界:探索编程之美与技术深度 1896
上一篇: 深入剖析Redis客户端Jedis的特性和原理
下一篇: 声称代码已开源却迟迟没更新,网友等了好几个月,最终一怒之下把作者挂网上
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值