WormholeStack - MyBatis
type: channel
link: https://wormholestack.com/tag/MyBatis/
items: MyBatis延迟加载原理剖析
items: 深入浅出MyBatis缓存机制
items: 浅析MyBatis执行SQL流程

MyBatis延迟加载原理剖析
type: item
date: 2021-09-06T17:05:00+08:00
description: 本系列是针对 MyBatis3 的源码分析文章已收录至精进MyBatis系列 系列其它文章 https://www.wormholestack.com/tag/MyBatis/源码阅读环境：https://gitee.com/M-Analysis/source_mybatis3 已填充关键注释1. 延迟加载的介绍及使用本文将针对MyBatis提供的延迟加载（懒加载）原理剖析。1.1 延迟加载是什么？简单的来说延迟加载就是，在需要用到数据的时候进行加载，不需要用到数据就不进行加载。假设数据库中涉及两张表，用户表AND订单表（一对多的关系），假设一个有很多订单，那么在查询用户的时候，需不需要当前用户关联的订单数据查询出来？通常来说查询用户信息的时候，肯定是需要用到用户订单的时候在查询为好，尤其是一对多的多表查询，通常都建议采用延迟加载，因为单表查询肯定要比多表关联查询速度要快，先从单表查询，接着需要时在关联表进行关联查询，会提高数据库性能。1.2 如何启用延迟加载MyBatis默认不启用延迟加载，启用延迟加载则需要我们进行一些简单的配置即可。首先可以MyBatis SqlMapper核心配置文件中设置setting属性 设置全局懒加载 <settings> <setting name="lazyLoadingEnabled" value="true" /> <setting name="aggressiveLazyLoading" value="false"/> </settings> 或者在mapper配置文件中配置fetchType标签 设置局部懒加载 <resultMap id="userMap" type="com.mryan.pojo.User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <collection property="orderList" ofType="com.mryan.pojo.Order" select="com.mryan.mapper.IOrderMapper.findOrderByUid" column="id" fetchType="lazy"> <id property="id" column="uid"/> <result property="orderTime" column="ordertime"/> <result property="total" column="total"/> </collection> </resultMap> select * from orders where uid = #{uid} 注意这里 局部的懒加载策略优先级药高于全局的懒加载策略1.3 测试延迟加载的作用为测试多表关联，表结构及pojo结构如下：User public class User implements Serializable { private Integer id; private String username; // 用户关联的订单数据 private List<Order> orderList; //省略 Orders public class Order implements Serializable { private Integer id; private String orderTime; private Double total; // 表明该订单属于哪个用户 private User user; //省略 在完成上述配置之后，跑一个单元测试，试一下延迟加载是否生效 /* 测试 延迟加载查询 */ @Test public void TEST_QUERY_LAZY()throws IOException{ InputStream inputStream=Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession=factory.openSession(); User user=sqlSession.selectOne("com.mryan.mapper.IUserMapper.findById",1); //延迟加载生效 下方输出语句 不涉及到orders表 于是不会打印orders相关日志 System.out.println("user："+user.getUsername()); …

深入浅出MyBatis缓存机制
type: item
date: 2021-08-27T01:13:00+08:00
description: 本系列是针对 MyBatis3 的源码分析文章已收录至精进MyBatis系列 系列其它文章 https://www.wormholestack.com/tag/MyBatis/源码阅读环境：https://gitee.com/M-Analysis/source_mybatis3 已填充关键注释1. 缓存的意义众所周知，和数据库打交道避免不了磁盘IO操作，那如果频繁的IO操作一定会对性能造成影响，所以减少与数据库的交互次数从而降低数据库压力进而提升查询效率是必要的。缓存是其中一种实现方式，简单的理解其实缓存就是内存中专门的一块区域，当从数据库中查询到一些数据将其放入缓存中，下次查询相同的数据时可以直接从缓存中获取数据即可，这样可减少了一步和数据库交互的过程。MyBatis提供了三级缓存机制，虽然MyBatis的缓存机制有些鸡肋，大部分开发人员多数情况都只会使用MyBatis默认缓存配置，又虽然MyBatis缓存机制有一些不足之处，出于学习还是决定写下此文章。2. 一级缓存2.1 猜想由一串测试代码引出今天的主角---一级缓存 /** * 测试一级缓存 */ @Test public void TEST_QUERY_BY_FIRST_CACHE() { //代理模式获取代理类 IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class); //第⼀次sql语句查询 将查询结果放入缓存中 User user1 = userMapper.findById(1); System.out.println("第一次查询：" + user1); //第⼆次sql语句查询，由于是同⼀个sqlSession,会去查询缓存 如果缓存中没有则查库 缓存中有则直接取缓存 User user2 = userMapper.findById(1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 上述测试代码，在同一个sqlSession中执行2次查询操作并记录操作日志，不过会发现上图sql执行语句只记录了一次查询操作，言外之意其实在同一个session中第一次sql查询会查库，后将查询结果放入缓存中，第二次sql查询，就会直接去查询缓存。不过目前来说也只是猜想，稍后我们翻看源码的时候会验证此猜想。不过话说回来，在同一个sqlSession这个前提下，多次查询走缓存不去查库，那如果库中数据更改了呢，数据被修改，删除，或者新增呢，岂不是数据不一致了，所以我猜想当执行了数据修改，删除，新增操作会重置缓存（重新查一次库），避免脏读，实际上MyBatis就是这么做的。编写如下测试类，运行代码： /** * 测试一级缓存 提交 是否重置缓存 */ @Test public void TEST_QUERY_COMMIT_BY_FIRST_CACHE() { //代理模式获取代理类 IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class); //第⼀次sql语句查询 将查询结果放入缓存中 User user1 = userMapper.findById(1); System.out.println("第一次查询：" + user1); //更新操作 并提交sqlSession user1.setUsername("MRyan666"); userMapper.updateById(user1); sqlSession.commit(); User user2 = userMapper.findById(1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 根据上述测试代码，初步证实了我的猜想在同一的sqlSession作用范围下，如果中间操作sqlSession执行了commit操作，其实也就是（删除，更新，新增）那么会清除sqlSession的缓存，保障缓存中拿到的数据一定是最新的，避免脏读。到这里先透一个底，其实上面所提及到的缓存在MyBatis中被命名为一级缓存。2.2 证明说了这么多猜想，眼见为实，接下来跟随作者一起通过对MyBatis源码解读探究一级缓存的原理及本质，在开始之前，提出几个问题，带入问题分析源码事半功倍。一级缓存是什么 数据结构是怎样的？一级缓存什么时候被构建一级缓存的不足及优化一起走进源码的世界上文我们探究了MyBatis执行SQL语句的流程 原文链接，虽然当时仅仅只是提了一嘴缓存相关，但是却提供了一个…

浅析MyBatis执行SQL流程
type: item
date: 2021-08-16T21:41:00+08:00
description: 本系列是针对 MyBatis3 的源码分析文章已收录至精进MyBatis系列 系列其它文章 https://www.wormholestack.com/tag/MyBatis/源码阅读环境：https://gitee.com/M-Analysis/source_mybatis3 已填充关键注释本文主要通过浅析MyBatis如何执行一个SQL语句（流程）为引，引出MyBatis的整体框架设计。作为MyBatis系列第一篇文章，必然先了解一下MyBatis的由来，所以在文章开始之前，我们来思考一个问题，MyBatis为什么诞生？1. 诞生一个轮子的诞生定是有其原因的，JDBC的诞生链接了程序和数据库，可以为多种关系数据库提供统一访问。 但是传统的JDBC方式存在很多缺点例如：数据库连接创建、释放频繁造成系统资源浪费，从⽽影响系统性能。Sql语句在代码中硬编码，造成代码不易维护，实际应用中sql变化的可能较⼤，sql变动需要改变 java代码。使用preparedStatement向占有位符号传参数存在硬编码，因为sql语句的where条件不⼀定，可能多也可能少，修改sql还要修改代码，系统不易维护。对结果集解析存在硬编码(查询列名)，sql变化导致解析代码变化，系统不易维护。为解决以上的问题，MyBatis被创造出来了，那它究竟是怎么规避解决以上原生JDBC的缺点呢，请往下看，正文来喽。2. 架构设计上面有提到JDBC的问题数据库连接创建，释放频繁导致的资源浪费，为解决这类问题，我们能想到的解决方案：肯定是池化，利用使用数据库连接池加载并初始化连接资源，池化管理。SQL语句在代码中的硬编码问题，为解决这类问题，我们能想到的解决方案：将SQL语句抽离到XML配置文件中，不需要改动代码，配置作为分离SQL及业务之间的桥梁。占位符传参硬编码问题，为解决这类问题，我们能想到的解决方案：利用反射，内省等技术，自动将实体与表进行属性和字段自动映射。当然我不是说说而已，MyBatis其实也是这么做的，ps：（这里我们先简单提一嘴，后面的文章《手撸简易版MBatis》会详细介绍）重点来喽，MyBatis架构师如何设计的Mybatis的功能架构分为三层：API接口层：提供给外部使用的接口 API，开发⼈员通过这些本地API来操纵数据库。接口层⼀接收 到调用请求就会调用数据处理层来完成具体的数据处理。MyBatis和数据库的交互有两种方式：使用传统的MyBatis提供的API使用Mapper代理的方式数据处理层：负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成⼀次数据库操作。基础⽀撑层：负责最基础的功能⽀撑，包括连接管理、事务管理、配置加载和缓存处理，这些都是共用的东西，将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。2.1 主要组件及其相互关系上述层次图中涉及到的重要模块，下述详细说明：SqlSession--》作为MyBatis⼯作的主要顶层API，表示和数据库交互的会话，完成必要数 据库增删改查功能。 Executor--》调度执行StatementHandler、ParameterHandler、ResultHandler执行相应的SQL语句 StatementHandler--》封装了JDBC Statement操作，负责对JDBC statement的操作，如设置参数、将Statement结果集转换成List集合。 ParameterHandler--》负责对用户传递的参数转换成JDBC Statement所需要的参数。处理SQL参数。 ResultSetHandler--》结果集ResultSet封装处理返回。 TypeHandler--》负责java数据类型和jdbc数据类型之间的映射和转换。 MappedStatement--》 MappedStatement维护了⼀条＜select | update | delete | insert＞节点的封装。 SqlSource--》负责根据用户传递的parameterObject，动态地生成SQL语句，将信息封装到BoundSql对象中，并返回。 BoundSql--》表示动态生成的SQL语句以及相应的参数信息。3. 执行SQL语句大概流程加载配置，以XML为例，将主配置文件内容解析到Configuration中，将SQL配置信息加载成为多个MapperdStatement对象，存入内存中。接口层调用MyBatis提供的API 传入参数对象，将请求传递给下层的数据处理层进行处理。简单的来说数据处理层完成的任务如下：Parameterhandler完成参数映射（参数映射配置，参数映射 类型解析）根据SQL的ID搜寻对应的M…