【Java架构必看】Mybatis的工作原理

简介: MyBatis执行分启动与运行两阶段:启动时加载配置,运行时代理执行SQL。通过JDK动态代理生成Mapper接口,结合缓存机制与Executor执行SQL,最终由TypeHandler完成结果映射。

mybatis1.png

无论是Mybatis也好,Spring也罢,它们的执行过程无非可分为启动阶段和运行阶段:

启动阶段:

  1. 定义配置文件,如XML、注解
  2. 解析配置文件,将配置文件加载到内存当中

运行阶段:

  1. 读取内存中的配置文件,并根据配置文件实现对应的功能


对于执行SQL的逻辑来讲,有如下步骤:

当配置完成之后,假如说我们要执行一个下面一个sql,那么该如何执行呢?

TestMapper testMapper = session.getMapper(TestMapper.class); Test test = testMapper.findOne(1);


一、代理类的生成

首先 MyBatis 会根据我们传入接口通过 JDK 动态代理,生成一个代理对象 TestMapper,生成逻辑如下所示:

public T newInstance(SqlSession sqlSession) {  // mapperProxy 实现了 InvocationHandler 接口,用于 JDK 动态代理  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);  return newInstance(mapperProxy); }  // 通过 JDK 动态代理生成对象 protected T newInstance(MapperProxy<T> mapperProxy) {  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),  new Class[]{ mapperInterface }, mapperProxy); }

代理类的主要逻辑在 MapperProxy 中,而代理逻辑则是通过 MapperMethod 完成的。

对于 MapperMethod 来说,它在创建的时候是需要读取 XML 或者方法注解的配置项,所以在使用的时候才能知道具体代理的方法的 SQL 内容。同时,这个类也会解析和记录被代理方法的入参和出参,以方便对 SQL 的查询占位符进行替换,同时对查询到的 SQL 结果进行转换。


二、执行SQL

代理类生成之后,就可以执行代理类的具体逻辑,也就是真正开始执行用户自定义的SQL逻辑了。

首先会进入到 MapperMethod 核心的执行逻辑,如下所示:

public Object execute(SqlSession sqlSession, Object[] args) {  Object result;  switch (command.getType()) {  case INSERT: {  Object param = method.convertArgsToSqlCommandParam(args);  result = rowCountResult(sqlSession.insert(command.getName(), param));  break;  }  case UPDATE: {  Object param = method.convertArgsToSqlCommandParam(args);  result = rowCountResult(sqlSession.update(command.getName(), param));  break;  }  case DELETE: {  Object param = method.convertArgsToSqlCommandParam(args);  result = rowCountResult(sqlSession.delete(command.getName(), param));  break;  }  case SELECT:  if (method.returnsVoid() && method.hasResultHandler()) {  executeWithResultHandler(sqlSession, args);  result = null;  } else if (method.returnsMany()) {  result = executeForMany(sqlSession, args);  } else if (method.returnsMap()) {  result = executeForMap(sqlSession, args);  } else if (method.returnsCursor()) {  result = executeForCursor(sqlSession, args);  } else {  Object param = method.convertArgsToSqlCommandParam(args);  result = sqlSession.selectOne(command.getName(), param);  }  break;  case FLUSH:  result = sqlSession.flushStatements();  break;  default:  throw new BindingException("Unknown execution method for: " + command.getName());  }  // ...  return result; }

通过代码我们可以很清晰地发现,为什么 MyBatis 的 insert、update 和 delete 会返回行数的原因。业务处理上,我们经常通过 update = 1 来判断当前语句是否更新成功。

这里一共做了两件事情,一件事情是通过 BoundSql 将方法的入参转换为 SQL 需要的入参形式,第二件事情就是通过 SqlSession 来执行对应的 Sql。下面我们通过 select 来举例。


三、缓存

SqlSession 是 MyBatis 对 SQL 执行的封装,真正的 SQL 处理逻辑要通过 Executor 来执行。Executor 有多个实现类,因为在查询之前,要先 check 缓存是否存在,所以默认使用的是 CachingExecutor 类,顾名思义,它的作用就是二级缓存。


image.gif


CachingExecutor 的执行逻辑如下所示:

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

二级缓存是和命名空间绑定的,如果多表操作的 SQL 的话,是会出现脏数据的。同时如果是不同的事务,也可能引起脏读,所以要慎重。

如果二级缓存没有命中,则会进入到 BaseExecutor 中继续执行,在这个过程中,会调用一级缓存执行。

值得一提的是,在 MyBatis 中,缓存分为 PerpetualCache、BlockingCache、LruCache 等,这些 cache 的实现则是借用了装饰者模式。一级缓存使用的是 PerpetualCache,里面是一个简单的 HashMap。一级缓存会在更新的时候,事务提交或者回滚的时候被清空。换句话说,一级缓存是和 SqlSession 绑定的。


四、查询数据库

如果一级缓存中没有的话,则需要调用JDBC执行真正的SQL逻辑。我们知道,在调用JDBC之前,是需要建立连接的,如下代码所示:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  Statement stmt;  Connection connection = getConnection(statementLog);  stmt = handler.prepare(connection, transaction.getTimeout());  handler.parameterize(stmt);  return stmt; }

我们会发现,Mybatis并不是直接从JDBC获取连接的,通过数据源来获取的,Mybatis默认提供了是那种数据源:JNDI,PooledDataSource和UnpooledDataSource,我们也可以引入第三方数据源,如Druid等。包括驱动等都是通过数据源获取的。


获取到Connection之后,还不够,因为JDBC的数据库操作是需要Statement的,所以Mybatis专门抽象出来了 StatementHandler 处理类来专门处理和JDBC的交互,如下所示:

SimpleStatementHandler public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  String sql = boundSql.getSql();  statement.execute(sql);  return resultSetHandler.<E>handleResultSets(statement); }

其实这三代代码就代表了Mybatis执行SQL的核心逻辑:组装SQL,执行SQL,组装结果。仅此而已。

具体Sql是如何组装的呢?是通过BoundSql来完成的,具体组装的逻辑大家可以从 org.apache.ibatis.mapping.MappedStatement#getBoundSql 中了解,这里不再赘述。


五、处理查询结果

当我们获取到查询结果之后,就需要对查询结果进行封装,即把查询到的数据库字段映射为DO对象。

因为此时我们已经拿到了执行结果ResultSet,同时我们也在应用启动的时候在配置文件中配置了DO到数据库字段的映射ResultMap,所以通过这两个配置就可以转换。核心的转换逻辑是通过TypeHandler完成的,流程如下所示:

  1. 创建返回的实体类对象,如果该类是延迟加载,则先生成代理类
  2. 根据ResultMap中配置的数据库字段,将该字段从ResultSet取出来
  3. 从ResultMap中获取映射关系,如果没有,则默认将下划线转为驼峰式命名来映射
  4. 通过setter方法反射调用,将数据库的值设置到实体类对象当中
目录
相关文章
|
7天前
|
存储 算法 关系型数据库
【Java架构师体系课 | MySQL篇】② 深入理解MySQL索引底层数据结构与算法
InnoDB索引为何采用B+树?本文由浅入深解析二叉树、红黑树、B树的缺陷,详解B+树的结构优势:非叶子节点不存数据、叶子节点有序且双向链接,支持高效范围查询与磁盘预读,三层即可存储两千多万数据,极大提升查询性能。
82 7
|
27天前
|
人工智能 运维 Kubernetes
Serverless 应用引擎 SAE:为传统应用托底,为 AI 创新加速
在容器技术持续演进与 AI 全面爆发的当下,企业既要稳健托管传统业务,又要高效落地 AI 创新,如何在复杂的基础设施与频繁的版本变化中保持敏捷、稳定与低成本,成了所有技术团队的共同挑战。阿里云 Serverless 应用引擎(SAE)正是为应对这一时代挑战而生的破局者,SAE 以“免运维、强稳定、极致降本”为核心,通过一站式的应用级托管能力,同时支撑传统应用与 AI 应用,让企业把更多精力投入到业务创新。
372 29
|
13天前
|
SQL 数据采集 人工智能
评估工程正成为下一轮 Agent 演进的重点
面向 RL 和在数据层(SQL 或 SPL 环境)中直接调用大模型的自动化评估实践。
752 211
|
27天前
|
人工智能 IDE Java
AI Coding实践:CodeFuse + prompt 从系分到代码
在蚂蚁国际信贷业务系统建设过程中,技术团队始终面临双重考验:一方面需应对日益加速的需求迭代周期,满足严苛的代码质量规范与金融安全合规要求;另一方面,跨地域研发团队的协同效率与代码标准统一性,在传统开发模式下逐渐显现瓶颈。为突破效率制约、提升交付质量,我们积极探索人工智能辅助代码生成技术(AI Coding)的应用实践。本文基于蚂蚁国际信贷技术团队近期的实际项目经验,梳理AI辅助开发在金融级系统快速迭代场景中的实施要点并分享阶段性实践心得。
312 25
AI Coding实践:CodeFuse + prompt 从系分到代码
|
14天前
|
数据可视化 搜索推荐 大数据
2026版基于python大数据的旅游可视化及推荐系统
本研究聚焦基于Python大数据的旅游可视化与推荐系统,利用Python在数据处理、分析和可视化方面的优势,结合Django框架与MySQL数据库,构建高效、个性化的旅游推荐平台。通过爬取多源旅游数据,运用机器学习算法挖掘用户偏好,实现精准推荐;借助Matplotlib、Seaborn等工具进行数据可视化,直观展示景点分布、客流趋势等信息。系统不仅提升游客决策效率与体验,也助力旅游企业优化产品设计与营销策略,推动行业数字化转型与智能化发展。
|
23天前
|
人工智能 开发框架 安全
浅谈 Agent 开发工具链演进历程
模型带来了意识和自主性,但在输出结果的确定性和一致性上降低了。无论是基础大模型厂商,还是提供开发工具链和运行保障的厂家,本质都是希望提升输出的可靠性,只是不同的团队基因和行业判断,提供了不同的实现路径。本文按四个阶段,通过串联一些知名的开发工具,来回顾 Agent 开发工具链的演进历程。
305 42
|
22天前
|
人工智能 运维 Cloud Native
直播|均降 40% 的 GPU 成本,大规模 Agent 部署和运维的捷径是什么?
10月28日19:30,阿里云云原生AgentRun与你《极客有约》。
177 29
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
《AI大模型技术全景解读》从机器学习到现代大模型
人工智能历经从机器学习到深度学习的演进,以Transformer架构为里程碑,推动大模型时代到来。技术发展涵盖CNN、RNN、BERT、GPT等核心模型,逐步实现语言理解、生成与多模态能力突破,正朝高效推理、安全对齐与普惠应用迈进。(238字)
下一篇