news 2026/4/18 10:50:16

MyBatis体系结构与工作原理 下篇

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis体系结构与工作原理 下篇

能力有限,只能粗看

核心处理层

ORM

反射模块

如何看

public class Reflector {}
每一个Reflector对应一个java类

简化反射操作

ReflectorFactory-创建Reflector

Reflector 缓存

测试

反射的invoker

metaclass

争对复杂表达式操作

metaObject

争对对象表达式解析操作

从mybatis角度看看

Configuration
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

反射selectlist返回值处理

不能细看,细看太复杂

​ private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { // 创建延迟加载器,用于懒加载关联属性 final ResultLoaderMap lazyLoader = new ResultLoaderMap(); ​ // 根据 ResultMap 创建当前行对应的对象 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); ​ // 如果对象不为空且没有类型处理器 if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { ​ // 创建 MetaObject,用于操作对象属性 反射内的包 final MetaObject metaObject = configuration.newMetaObject(rowValue); ​ // 初始化标记,记录是否通过构造函数填充了值 boolean foundValues = this.useConstructorMappings; ​ // 判断是否需要自动映射未在 ResultMap 中指定的列 if (shouldApplyAutomaticMappings(resultMap, false)) { ​ // 自动映射,映射ResultMAP 的列 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } ​ // 按 ResultMap 映射列到对象属性 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; ​ // 如果有延迟加载属性,也认为找到了值 foundValues = lazyLoader.size() > 0 || foundValues; ​ // 如果没有值且配置不返回空对象,则置为 null rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } ​ // 返回当前行映射得到的对象 return rowValue; } ​

类型转化模块

BaseType 空处理,具体还是给子类

​ public class ZdyTypeHandler extends BaseTypeHandler<String> { /// 插入回调 @Override public void setNonNullParameter(PreparedStatement ps, int i, String s, JdbcType jdbcType) throws SQLException { ps.setString(i, "666" + s); // 写入数据库时加前缀 } ​ @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); return value != null ? "666" + value : null; // 读出时加前缀 } ​ @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String value = rs.getString(columnIndex); return value != null ? "666" + value : null; } ​ @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String value = cs.getString(columnIndex); return value != null ? "666" + value : null; } } ​

调试

public BaseBuilder(Configuration configuration) { ​ // 接收 MyBatis 全局配置对象 Configuration this.configuration = configuration; // 从 Configuration 中获取类型别名注册器 TypeAliasRegistry // 用于管理 <typeAliases> 中配置的别名,例如 User -> com.xxx.User this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); // 从 Configuration 中获取类型处理器注册器 TypeHandlerRegistry // 用于管理 Java 类型与 JDBC 类型之间的转换处理器,例如 StringTypeHandler this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); } ​

执行过程

当 JDBC 查询返回ResultSet后,MyBatis 会逐行处理:

  • 每一行对应一个 Java 对象

  • 这个对象的生成与赋值由getRowValue完成

这一阶段做三件事:

① 创建结果对象

MyBatis根据ResultMap描述的信息:

  • 确定要创建的实体类型(比如 User)

  • 通过构造器或反射实例化对象

结果:得到rowValue

② 判断是否需要进一步属性填充

如果返回类型是简单类型(如 Integer/String):

  • TypeHandler 直接取值即可,不需要属性映射

如果是普通实体对象:

  • 继续执行字段映射流程

③ MetaObject包装(反射入口)

MyBatis 会创建 MetaObject:

  • 统一封装对象属性访问

  • 后续所有 setter/getter 都通过它完成

  • 底层依赖反射调用 setter

getRowValue

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { // 获取当前 ResultMap 中已经映射到的数据库列名集合 //USER_ID //USER_NAME //REAL_NAME //PASSWORD //AGE //D_ID final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; // 获取 ResultMap 中所有属性映射配置(<result>、<association>、<collection>) // final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); //org.apache.ibatis.session.Configuration@10425785,userId,user_Id,class java.lang.Integer,INTEGER,class java.lang.Integer //org.apache.ibatis.session.Configuration@10425785,userName,user_name,class java.lang.String,null,class java.lang.String //org.apache.ibatis.session.Configuration@10425785,realName,real_name,class java.lang.String,VARCHAR,class java.lang.String //org.apache.ibatis.session.Configuration@10425785,password,password,class java.lang.String,VARCHAR,class java.lang.String //org.apache.ibatis.session.Configuration@10425785,age,age,class java.lang.Integer,INTEGER,class java.lang.Integer //org.apache.ibatis.session.Configuration@10425785,dId,d_id,class java.lang.Integer,INTEGER,class java.lang.Integer for (ResultMapping propertyMapping : propertyMappings) { //propertyMapping = {ResultMapping@3586} "ResultMapping{property='password', column='password', javaType=class java.lang.String, jdbcType=VARCHAR, nestedResultMapId='null', nestedQueryId='null', notNullColumns=[], columnPrefix='null', flags=[], composites=[], resultSet='null', foreignColumn='null', lazy=true}" // configuration = {Configuration@3509} // property = "password" // column = "password" // javaType = {Class@683} "class java.lang.String" // jdbcType = {JdbcType@3774} "VARCHAR" String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it column = null; } if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { /// 获取转化后的值 Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); // issue #541 make property optional final String property = propertyMapping.getProperty(); if (property == null) { continue; } else if (value == DEFERRED) { foundValues = true; continue; } if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) { // gcode issue #377, call setter on nulls (value is not 'found') //设置值 metaObject.setValue(property, value); } } } return foundValues; } //从 ResultSet 中取出某个属性(字段)的值,并支持嵌套查询、嵌套结果集、普通列映射三种情况。 private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } else if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK? return DEFERRED; } else { // 获取该属性对应的 TypeHandler,用于从 ResultSet 中取值并转换类型 final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler(); // 使用 TypeHandler 从 ResultSet 中读取该列的值,并返回 final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); return typeHandler.getResult(rs, column); } }

class java.lang.String

哪个适合注册的

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }

注册了类型处理器

日志模块

适配器模式

四个日志级别

slf4j指定

private static void tryImplementation(Runnable runnable) { if (logConstructor == null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }

配置

如何关联

config

jdbc

c

操作日志

返回日志代理对象

事务

/** * Copyright 2009-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.transaction; ​ import java.sql.Connection; import java.sql.SQLException; /** * MyBatis Transaction 事务接口 * * 版权声明:Apache License 2.0 * * Transaction 接口是 MyBatis 对数据库事务的抽象定义。 * * 它的核心作用是: * 1)封装 JDBC Connection * 2)统一管理事务生命周期 * * 生命周期包括: * - 创建连接 * - 准备连接(设置自动提交等) * - 提交事务 commit * - 回滚事务 rollback * - 关闭连接 close * * MyBatis 的事务控制最终都会通过该接口实现。 * * @author Clinton Begin */ public interface Transaction { ​ /** * 获取当前事务内部持有的数据库连接 Connection。 * * MyBatis 执行 SQL 时最终都是通过 Connection 创建 Statement 来完成操作。 * * @return 数据库连接对象 Connection * @throws SQLException 如果获取连接失败则抛出异常 */ Connection getConnection() throws SQLException; ​ /** * 提交事务。 * * 当 SQL 执行成功并且需要持久化修改时, * MyBatis 会调用该方法触发 JDBC Connection.commit()。 * * @throws SQLException 提交失败时抛出异常 */ void commit() throws SQLException; ​ /** * 回滚事务。 * * 当 SQL 执行过程中出现异常, * 或用户主动要求撤销操作时, * MyBatis 会调用该方法触发 JDBC Connection.rollback()。 * * @throws SQLException 回滚失败时抛出异常 */ void rollback() throws SQLException; ​ /** * 关闭事务。 * * 事务结束后必须释放数据库连接资源, * MyBatis 会调用该方法关闭 Connection。 * * @throws SQLException 关闭连接失败时抛出异常 */ void close() throws SQLException; ​ /** * 获取事务超时时间(如果配置了)。 * * MyBatis 支持在某些事务管理器中设置超时限制, * 超时后事务可能会自动回滚。 * * @return 超时时间(秒),如果未设置则返回 null * @throws SQLException 获取失败时抛出异常 */ Integer getTimeout() throws SQLException; ​ } ​

MyBatis 默认用 JDBC 原生事务:

  • connection.commit()

  • connection.rollback()

即:

MyBatis 的事务本质就是 JDBC Connection 的事务

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 10:07:48

LeetCode热题100--136. 只出现一次的数字--简单

题目 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff1a; 输入&…

作者头像 李华
网站建设 2026/4/18 8:07:55

“死了么” 改名,申请注册商标注意避开负面词!

近日&#xff0c;在互联网引起网友热议的APP“死了么” 发布消息&#xff0c;称“死了么”将改名“Demumu”&#xff0c;这个应用的核心功能是&#xff0c;用户每日签到&#xff0c;若连续2日未签到则系统次日向紧急联系人发送邮件提醒&#xff0c;普推知产商标老杨认为这个改名…

作者头像 李华
网站建设 2026/4/18 7:43:48

【FFmpeg使用指南】Part 1:核心架构与媒体流处理

&#x1f4da; 写给开发者的音视频处理工程手册 &#x1f3af; 目标&#xff1a;以严谨的技术视角&#xff0c;解析 FFmpeg 这一跨平台多媒体框架的底层逻辑与工作流。不堆砌参数&#xff0c;而是从原理层面理解“编解码”与“封装”的本质。 &#x1f6e0;️ 核心问题&#xf…

作者头像 李华
网站建设 2026/4/18 8:48:21

Docker 基础入门教程:容器化技术完全指南

目录 引言一、Docker 概述与核心概念核心组件&#xff1a;与传统虚拟机的区别&#xff1a; 二、Docker 安装与环境准备2.1 安装 Docker2.2 验证安装 三、Docker 基础命令详解3.1 镜像管理命令3.2 容器管理命令 四、Dockerfile 详解与最佳实践4.1 基本语法4.2 重要指令说明4.3 构…

作者头像 李华
网站建设 2026/4/18 2:34:56

花15分钟搭一套国产AI系统,把Clawdbot巨额token成本干到0

如果你已经在用 Clawdbot&#xff0c;那你大概率懂我接下来要说什么。爽是真的爽。贵&#xff0c;也是真的贵。第一次让 Clawdbot 跑复杂任务的时候&#xff0c;我是真的被惊到了。长期记忆、拆解任务、执行闭环、自我迭代——你只管说目标&#xff0c;它自己把活干完的体验&am…

作者头像 李华