框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性

本文涉及的产品
云原生网关 MSE Higress,422元/月
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。

最近在思考一个问题:如何能够更好的分享主流框架源码学习笔记(主要是源码部分)?让有缘刷到的同学既可以有所收获,还能保持对相关技术架构探讨学习热情和兴趣。以及自己也保持较高的分享热情和动力。

今天尝试用一个SQL查询作为引子,去解构Mybatis的核心原理和关键源码处理流程。这种更加贴合工作实践方式,相信可以降低探索核心源码门槛。


一、前言背景

二、Mybatis概述

三、Mybatis的核心原理

3.1 Mybatis核心功能处理流程

3.1.1 解析配置-加载并解析Mapper配置文件

3.1.2 创建连接-创建SqlSessionFactory并获取sqlSession连接

3.1.3 执行SQL语句-Executor

3.1.4 结果数据封装 MapperStatement & ResultSetHandler

3.1.5 关闭连接

四、核心功能特性

4.1 支持动态灵活的SQL

4.2 详解一级、二级缓存机制

4.2.1 二级缓存为什么默认不开启?

4.3 支持插件扩展

4.4 延迟加载

4.5 SQL注解


一、前言背景

   在10多年前,那时候刚开始工作,移动互联网还没发展起来,Mybatis还没流行,后端应用开发,主流用的是SSH框架。回想那时候的Hibernate、Spring,配置多又杂,其实对新手并不友好。然而相比手写JDBC连接管理、繁琐的结果数据转换,让SSH当年也是火了好几年。

   随着Hibernate和Mybatis的不断发展,研发人员成功解放手写JDBC数据库连接查询相关研发工作。他们都是优秀的ORM对象关系映射管理框架,也就是持久层框架。但是Hibernate存在对复杂sql关系支持弱、不支持存储过程、性能差、调优难、全表映射复杂等问题,用的人越来越少。

   而后起之秀Mybatis,当今最经典ORM框架,由于其灵活易用、好扩展、支持复杂SQL、出色的性能,较好的平衡对象关系映射管理和SQL编写支持,称为半ORM框架,已经成功替代Hibernate。

   今天我们梳理Mybatis的核心原理和工作流程,以及重点分析它的一些核心功能特性。

二、Mybatis概述

   Mybatis是一个持久层框架,具体就是用来操作数据库数据,并转换成目标对象的技术框架。它的核心在于将表数据和对象实例进行关联映射,也就是ORM(object relation Mapping)。

   Mybatis之所以可以替换Hibernate,主要是支持SQL定制、高级的映射管理功能、缓存机制、还有存储过程(由于大数据技术发展,目前存储过程用的人也越来越少,但是在那个年代支持存储过程非常实用)。Mybatis灵活可扩展高性能的特性,让我们开发读写数据,几乎不需要编程,主要做的工作就是编写Mapper配置文件,把SQL和对象关系映射管理好,就可以实现CRUD。而多类型数据库的切换迁移,对系统应用来说,简单到只需要替换JDBC的驱动。

三、Mybatis的核心原理

   如上所述,Mybatis核心工作就是帮助研发人员对数据库的读写操作,简化成面向对象操作。

   对于一个完全不懂Mybatis或者ORM的人来说,如果要实现读写数据库,该怎么实现?这个相信很多人都能回答:通过jdbc连接数据库、执行SQL、解析sql数据结果,就三个步骤完成。

   而Mybatis的核心原理逻辑更加细化,但整体也是类似以上三个步骤。毕竟大家目标一致,只是实现过程不同而已。

   接下来我们用一个非常简单的demo,就是通过Mybatis去读数据库数据,demo就只有几行代码,然后循序渐进了解Mybatis的核心工作原理,以及核心源码组件功能。


package com.lading.mybaties;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisDemo {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            sqlSession.selectOne("com.lading.mapper.UserMapper.getUserById", 1);
        }
    }
}

3.1 Mybatis核心功能处理流程

   Mybatis整体框架,基于面向对象思想去实现与数据库表数据交互,它的核心步骤按顺序处理有以下五个。

3.1.1 解析配置-加载并解析Mapper配置文件

   Mybatis通过SqlSessionFactoryBuilder来加载解析配置文件,并生SqlSessionFactory。

   SqlSessionFactoryBuilder是Mybatis的入口类,类似tomcat的org.apache.catalina.startup.Bootstrap 启动类。

   比如我们项目只有Mybatis包、jdbc驱动,想要基于Mybatis去读写数据,首先需要通过以下三行代码去解析你的Mybatis相关配置文件,以及构建一个SqlSessionFactory,为后续创建数据库连接和读写做准备。


String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

   这三行代码里最后一行,看源码会发现,里面做了非常多的工作,细无巨细的把Mybatis管理配置文件、还有对象sql映射Mapper文件一一解析,包括properties、settings、environment、mapper等标签数据,以及业务相关的mapper文件里的resultMap、CURD 标签都会转成configration对象属性。

   最后利用解析结果数据存放的对象configration去实例化构建sql 会话工厂:

   new DefaultSqlSessionFactory(config)。

3.1.2 创建连接-创建SqlSessionFactory并获取sqlSession连接

   解析完成配置文件后,Mybatis框架已经清晰知道Mybatis的基础配置信息、连接数据库的类型、用户密码信息,还有相关表映射关系。

   通过SqlSessionFactoryBuilder构建了session工厂实例SqlSessionFactory,看名字可以知道是通过建造者模式去实例化该对象。

   SqlSessionFactory是Mybatis的核心接口,它就是用来负责实现管理会话连接。

   在应用启动或者需要用到Mybatis读写数据时候,就生成一个实例DefaultSqlSessionFactory(它是session工厂接口唯一实现类)。

   SqlSessionFactory,通常在应用里是全局唯一并共享。

    然后通过会话工厂实例sqlSessionFactory去开启一个session连接:

        SqlSession sqlSession = sqlSessionFactory.openSession();

    SqlSession就是真正负责执行sql、并且管理事务的核心功能类,它底层是jdbc的连接。

    session每次用完就关闭,需要用的时候再次新建。但是Mybatis也有实现对应的连接池,如果配置了连接池就不会关闭。       Mybatis的连接池,后面出一篇文章专门分享Mybatis如何管理连接池。

    比如,通过session去查询用户ID=1的用户数据:


SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User ladingUser = userMapper.selectUserById(1);
System.out.println(ladingUser);

3.1.3 执行SQL语句-Executor

     在3.1.2里,看起来sql session执行了sql查询,但是DefaultSqlSession里面封装了一个Executor执行器。

    Executor执行器,它是真正负责执行sql的打工人,里面有一个抽象类BaseExecutor,通过模板方法模式去共享自己的模板方法能力。另外三个子类去继承实现不同的db操作。

    这三个执行器的主要区别在于:

    SimpleExecutor,是一个最简单的执行器,每次执行sql都新建一个Statement对象。

    比如它里面的query方法源码:


@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, 
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  // JDBC中 Statement接口
  Statement stmt = null;
  try {
    // 获取到Configuration对象
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 里面创建Connection代理对象,新增这个Statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 执行查询,封装结果集
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

    ReuseExecutor,顾名思义是可复用执行器。特点是,复用预处器PreparedStatement。

    比如获取预处理Statement的方法源码:

     而BatchExcutor,叫做批量执行器,支持批量处理SQL语句。

3.1.4 结果数据封装 MapperStatement & ResultSetHandler

    结果数据封装逻辑里面相对繁琐。实际上在Excutor执行sql之前,也依赖mapperStatement去封装sql语句、sql的入参。底层通过jdbc connection执行sql后,通过ResultSetHandler和TypeHandler去解析封装结果数据。

3.1.5 关闭连接

     最后是关闭sqlsession,释放资源。如果是配置应用连接池,就是归还连接操作。

四、核心功能特性

4.1 支持动态灵活的SQL

    直接允许在mapper XML文件配置动态的sql。包括if、where、choose、when、foreach等多种动态条件。这个能力让Mybatis成功支持复杂关联sql处理。

4.2 详解一级、二级缓存机制

    Mybatis支持缓存,大幅提升数据库查询性能。默认开启一级缓存,关闭二级缓存。

    一级缓存:是基于sqlsession去实现,同一个sql session多次查询,会复用相同sql 的缓存结果。底层是通过把一个SQL的id、名称、入参计算得到一个唯一key,并和结果数据存入一个map里。

    当同一个sql session后面重复的查询,就会判断缓存是否有数据,如果有就直接返回,不再继续从数据库里查询数据,大幅提升单个sql session里的重复查询效率。

    如果在一个sql session里出现了update、delete、insert、或者commit、close session操作,就会自动去清空缓存,确保没有脏数据在Mybatis一级缓存里。

    该缓存默认开启。如果要关闭,可以通过flushCache=true去关闭。

     二级缓存:是基于mapper,也就是命名空间级别的缓存。相当于sqlSessionFactory级别,比一级缓存sqlSession更高一个层级。在同一个mapper下,所有session会话产生的缓存数据统一在mapper里命名空间管理。多个mapper的二级缓存互不干扰。

     二级缓存,默认是关闭的。可以在mapper里,新增cache标签就可以开启。


<mapper namespace="com.lading.mapper.UserMapper">
<!--启用二级缓存-->
<cache eviction="FIFO" flushInterval="30000" readOnly="true"/>
</mapper>

4.2.1 二级缓存为什么默认不开启?

    之所以默认关闭,主要因为二级缓存可能有脏读。

    正因为所有session会话产生的缓存数据,统一在mapper里命名空间管理,多个mapper的二级缓存互不干扰。这里就可能导致研发人员如果在另一个mapper新增或者修改了数据,其他地方mapper缓存的数据就没有被自动更新,就会造成生产故障。

     当然这个Mybatis有提供相关配置,支持关联mapper同步被动去清空二级缓存,避免干扰。

4.3 支持插件扩展

     Mybatis允许研发人员在核心组件中插入自定义的逻辑,比如分页、拦截器、性能监控插件功能 。

     插件的开发,可以通过实现 org.apache.ibatis.pluginInterceptor接口,然后实现里面 intercept、plugin 和 setProperties方法来新增插件功能。

     比如增加一个执行sql监控功能。


package com.lading.mybaties;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
public class TimeMonitorPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed(); // 执行目标方法
        long endTime = System.currentTimeMillis();
        System.out.println("SQL 执行耗时: " + (endTime - startTime) + "ms");
        return result;
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        // 可以设置拦截器的属性
    }
}

     在 mybatis-config.xml 文件中注册自定义拦截器:


<plugins>
<plugin interceptor="lading.mybaties.TimeMonitorPlugin"/>
</plugins>

sql查询的时候就会打印sql执行时间。

4.4 延迟加载

    可以控制关联对象,在需要用到的时候才去加载。比如1-n的场景,一个学生有多门课程信息。查询一个学生基本信息的时候,如果没有用到课程列表,就不需要在关联查询里把课程列表页拉出去,提升了查询效率。


//fetchType=lazy开启延迟加载
<association property="xxx" fetchType="lazy">

    这个延迟加载功能,和Springboot通过@lazy注解去解决循环依赖问题,有类似异曲同工的作用。

4.5 SQL注解

     MyBatis 也支持使用注解来配置 SQL 映射,从而简化 XML 配置,可以实现零xml配置文件去操作数据库数据。常用的注解包括常规的CURD:

@Select

@Insert

@Update

@Delete

   还有,@Results结果映射注解。

   比如通过名称去查询用户:


@Select("SELECT * FROM user WHERE name = #{name}")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name")
})
User selectUserByName(String name);


目录
打赏
0
0
0
0
106
分享
相关文章
MySQL原理简介—1.SQL的执行流程
本文介绍了MySQL驱动、数据库连接池及SQL执行流程的关键组件和作用。主要内容包括:MySQL驱动用于建立Java系统与数据库的网络连接;数据库连接池提高多线程并发访问效率;MySQL中的连接池维护多个数据库连接并进行权限验证;网络连接由线程处理,监听请求并读取数据;SQL接口负责执行SQL语句;查询解析器将SQL语句解析为可执行逻辑;查询优化器选择最优查询路径;存储引擎接口负责实际的数据操作;执行器根据优化后的执行计划调用存储引擎接口完成SQL语句的执行。整个流程确保了高效、安全地处理SQL请求。
221 76
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
MySQL原理简介—10.SQL语句和执行计划
本文介绍了MySQL执行计划的相关概念及其优化方法。首先解释了什么是执行计划,它是SQL语句在查询时如何检索、筛选和排序数据的过程。接着详细描述了执行计划中常见的访问类型,如const、ref、range、index和all等,并分析了它们的性能特点。文中还探讨了多表关联查询的原理及优化策略,包括驱动表和被驱动表的选择。此外,文章讨论了全表扫描和索引的成本计算方法,以及MySQL如何通过成本估算选择最优执行计划。最后,介绍了explain命令的各个参数含义,帮助理解查询优化器的工作机制。通过这些内容,读者可以更好地理解和优化SQL查询性能。
|
21天前
|
六、MyBatis特殊的SQL:模糊查询、动态设置表名、校验名称唯一性
六、MyBatis特殊的SQL:模糊查询、动态设置表名、校验名称唯一性
27 0
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
Vanna 是一个开源的 Python RAG(Retrieval-Augmented Generation)框架,能够基于大型语言模型(LLMs)为数据库生成精确的 SQL 查询。Vanna 支持多种 LLMs、向量数据库和 SQL 数据库,提供高准确性查询,同时确保数据库内容安全私密,不外泄。
689 7
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
Spark SQL向量化执行引擎框架Gluten-Velox在AArch64使能和优化
本文摘自 Arm China的工程师顾煜祺关于“在 Arm 平台上使用 Native 算子库加速 Spark”的分享,主要内容包括以下四个部分: 1.技术背景 2.算子库构成 3.算子操作优化 4.未来工作
197 0
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
17 0
Java后端开发-使用springboot进行Mybatis连接数据库步骤
本文介绍了使用Java和IDEA进行数据库操作的详细步骤,涵盖从数据库准备到测试类编写及运行的全过程。主要内容包括: 1. **数据库准备**:创建数据库和表。 2. **查询数据库**:验证数据库是否可用。 3. **IDEA代码配置**:构建实体类并配置数据库连接。 4. **测试类编写**:编写并运行测试类以确保一切正常。
106 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
243 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
5月前
|
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
161 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块