This commit is contained in:
by931
2022-09-06 22:30:37 +08:00
parent 66970f3e38
commit 3d6528675a
796 changed files with 3382 additions and 3382 deletions

View File

@@ -182,8 +182,8 @@ function hide_canvas() {
<p>聊完了 MyBatis 框架本身的一些亮点之后,我们再来看 MyBatis 在实际开发中的使用情况。</p>
<p>首先,从 GitHub 上可以看到MyBatis 项目目前有 14.6 K 的 Star以及 9.8 K 的 Fork国内的很多大厂例如阿里、网易、华为等都会使用到 MyBatis 框架,其热度可见一斑。</p>
<p>那 MyBatis 在很多人都很关心的招聘层面又有怎样的表现呢?下面是国内几家大厂对 Java 开发工作的岗位描述:</p>
<p><img src="assets/CgpVE2AJKUqAKtZTAAQvV9Mx8qY091.png" alt="Drawing 0.png" />
<img src="assets/Cip5yGAJKVKAGbUxAASCteXV8q8281.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgpVE2AJKUqAKtZTAAQvV9Mx8qY091.png" alt="png" />
<img src="assets/Cip5yGAJKVKAGbUxAASCteXV8q8281.png" alt="png" /></p>
<p>(职位信息来源:拉勾网)</p>
<p>作为一名 Java 工程师,<strong>深入掌握一款持久化框架已经是一项必备技能</strong>,并且成为个人职场竞争力的关键项。拉勾网显示,研发工程师、架构师等高薪岗位,都要求你熟悉并曾经深入使用过某种持久化框架,其中以 MyBatis 居多,“熟悉 MyBatis” 或是“精通 MyBatis” 等字眼更是频繁出现在岗位职责中。</p>
<p>所以说,如果你想要进入一线大厂,能够熟练使用 MyBatis 开发已经是一项非常基本的技能,同时大厂也更希望自己的开发人员深入了解 MyBatis 框架的原理和核心实现。</p>
@@ -208,7 +208,7 @@ function hide_canvas() {
<p>也可能有些同学会结合这些残缺的“武功秘籍”和 MyBatis 源码这个总纲,自己直接去阅读源码,这不仅是一个痛苦的过程,而且很可能会由于对整个架构的“视野”受限,迷失在代码迷宫中。这就需要你本身具备一定的技术功底,而且要对整个开源项目有比较高的熟练度,还要耐得住性子,花费上一些时间,走上一些弯路,才能完全掌握其核心原理。当然也有可能是更糟糕的结果,花了很大力气去阅读源码,关上 IDEA 之后依然“似懂非懂”,然后就放弃了。</p>
<p>除此之外,按照“总纲+残卷”的模式完成了 MyBatis 的源码分析后,你可能还是会缺少下图展示的“架构、方案、模式”这一部分知识,它是在底层原理基础之上的。对于优秀的开发人员来说,不仅要能看到代码细节处的优秀设计,而且还要能站在更高的角度看整体框架的架构之美,这才是分析一个开源框架最重要的两部分收获。</p>
<p><img src="assets/Cip5yGAQ5d2AQ1WlAAHlKhm69Ys260.png" alt="图片2.png" /></p>
<p><img src="assets/CgpVE2ARDmOAKOoSAAVhUzE5yeE415.png" alt="2021127-65510.png" /></p>
<p><img src="assets/CgpVE2ARDmOAKOoSAAVhUzE5yeE415.png" alt="png" /></p>
<h3>这门课的核心内容是什么</h3>
<p>正是因为深刻了解到很多开发人员在学习过程中可能会碰到资料不全、无人指路、架构经验各不相同等一系列问题,再加上我曾经分享过各种开源项目的源码分析资料,并且收到大家的一致好评,所以我决定和“拉勾教育”合作,开设一个系列课程,根据自己丰富的开源项目分析经验,来带你一起<strong>分析 MyBatis 源码</strong><strong>拆解 MyBatis 架构</strong>,希望<strong>帮你理清 MyBatis 的底层原理、深刻理解 MyBatis 的架构设计</strong></p>
<p>具体来说,我是从以下四个层面来设计这门课程的。</p>

View File

@@ -183,13 +183,13 @@ function hide_canvas() {
</ol>
<p>无论是执行查询操作,还是执行其他 DML 操作1、2、3、4、6 这些步骤都会重复出现。为了简化重复逻辑,提高代码的可维护性,可以<strong>将上述重复逻辑封装到一个类似 DBUtils 的工具类中</strong>,在使用时只需要调用 DBUtils 工具类中的方法即可。当然,我们也可以<strong>使用“反射+配置”的方式,将步骤 5 中关系模型到对象模型的转换进行封装</strong>,但是这种封装要做到通用化且兼顾灵活性,就需要一定的编程功底。</p>
<p>为了处理上述代码重复的问题以及后续的维护问题,我们在实践中会进行一系列评估,选择一款适合项目需求、符合人员能力的 ORMObject Relational Mapping对象-关系映射)框架来封装 1~6 步的重复性代码,实现对象模型、关系模型之间的转换。这正是<strong>ORM 框架的核心功能:根据配置(配置文件或是注解)实现对象模型、关系模型两者之间无感知的映射</strong>(如下图)。</p>
<p><img src="assets/CgqCHmAON9CAWnMJAACJd8-3Mcg506.png" alt="Lark20210125-111459.png" /></p>
<p><img src="assets/CgqCHmAON9CAWnMJAACJd8-3Mcg506.png" alt="png" /></p>
<p>对象模型与关系模型的映射</p>
<p>在生产环境中,数据库一般都是比较稀缺的,数据库连接也是整个服务中比较珍贵的资源之一。建立数据库连接涉及鉴权、握手等一系列网络操作,是一个比较耗时的操作,所以我们不能像上述 JDBC 基本操作流程那样直接释放掉数据库连接,否则持久层很容易成为整个系统的性能瓶颈。</p>
<p>Java 程序员一般会使用数据库连接池的方式进行优化,此时就需要引入第三方的连接池实现,当然,也可以自研一个连接池,但是要处理连接活跃数、控制连接的状态等一系列操作还是有一定难度的。另外,有一些查询返回的数据是需要本地缓存的,这样可以提高整个程序的查询性能,这就需要缓存的支持。</p>
<p>如果没有 ORM 框架的存在,这就需要我们 Java 开发者熟悉相关连接池、缓存等组件的 API 并手动编写一些“黏合”代码来完成集成,而且这些代码重复度很高,这显然不是我们希望看到的结果。</p>
<p>很多 ORM 框架都支持集成第三方缓存、第三方数据源等常用组件,并对外提供统一的配置接入方式,这样我们只需要使用简单的配置即可完成第三方组件的集成。当我们需要更换某个第三方组件的时候,只需要引入相关依赖并更新配置即可,这就<strong>大大提高了开发效率以及整个系统的可维护性</strong></p>
<p><img src="assets/CgpVE2ARDn-AdyGBAAVffl0vlNA234.png" alt="2021127-6553.png" />
<p><img src="assets/CgpVE2ARDn-AdyGBAAVffl0vlNA234.png" alt="png" />
下面我们就简单介绍一下在实践中常用的几种 ORM 框架。</p>
<h3>Hibernate</h3>
<p>Hibernate 是 Java 生态中著名的 ORM 框架之一。Hibernate 现在也在扩展自己的生态,开始支持多种异构数据的持久化,不仅仅提供 ORM 框架,还提供了 Hibernate Search 来支持全文搜索,提供 validation 来进行数据校验,提供 Hibernate OGM 来支持 NoSQL 解决方案。</p>
@@ -197,7 +197,7 @@ function hide_canvas() {
<p>在使用 Hibernate 的时候Java 开发可以使用映射文件或是注解定义 Java 语言中的类与数据库中的表之间的各种映射关系,这里使用到的映射文件后缀为“.hbm.xml”。hbm.xml 映射文件将一张数据库表与一个 Java 类进行关联之后,该数据库表中的每一行记录都可以被转换成对应的一个 Java 对象。<strong>正是由于 Hibernate 映射的存在Java 开发只需要使用面向对象思维就可以完成数据库表的设计。</strong></p>
<p>在 Java 这种纯面向对象的语言中,两个 Java 对象之间可能存在一对一、一对多或多对多等复杂关联关系。Hibernate 中的映射文件也必须要能够表达这种复杂关联关系才能够满足我们的需求,同时,还要能够将这种关联关系与数据库中的关联表、外键等一系列关系模型中的概念进行映射,这也就是 ORM 框架中常提到的“<strong>关联映射</strong>”。</p>
<p>下面我们就来结合示例介绍“一对多”关联关系。例如一个顾客Customer可以创建多个订单Order而一个订单Order只属于一个顾客Customer两者之间存在一对多的关系。在 Java 程序中,可以在 Customer 类中添加一个 List 类型的字段来维护这种一对多的关系在数据库中可以在订单表t_order中添加一个 customer_id 列作为外键指向顾客表t_customer的主键 id从而维护这种一对多的关系如下图所示</p>
<p><img src="assets/CgqCHmAON92AUlSJAAGm39S5TAY573.png" alt="Lark20210125-111502.png" /></p>
<p><img src="assets/CgqCHmAON92AUlSJAAGm39S5TAY573.png" alt="png" /></p>
<p>关系模型中的一对多和对象模型中的一对多</p>
<p>在 Hibernate 中,可以通过如下 Customer.hbm.xml 配置文件将这两种关系进行映射:</p>
<pre><code>&lt;hibernate-mapping&gt;
@@ -262,16 +262,16 @@ List&lt;Customer&gt; list = criteria.add(Restrictions.like(&quot;name&quot;,&quo
<p>JPA 是在 JDK 5.0 后提出的 Java 持久化规范JSR 338<strong>JPA 规范本身是为了整合市面上已有的 ORM 框架</strong>,结束 Hibernate、EclipseLink、JDO 等 ORM 框架各自为战的割裂局面,简化 Java 持久层开发。</p>
<p>JPA 规范从现有的 ORM 框架中借鉴了很多优点例如Gavin King 作为 Hibernate 创始人,同时也参与了 JPA 规范的编写,所以在 JPA 规范中可以看到很多与 Hibernate 类似的概念和设计。</p>
<p>既然 JPA 是一个持久化规范,没有提供具体持久化实现,那谁来提供实现呢?答案是市面上的 ORM 框架例如Hibernate、EclipseLink 等都提供了符合 JPA 规范的具体实现,如下图所示:</p>
<p><img src="assets/CgqCHmAJKpiAD5DOAAEJWo_r1B4663.png" alt="Drawing 2.png" /></p>
<p><img src="assets/CgqCHmAJKpiAD5DOAAEJWo_r1B4663.png" alt="png" /></p>
<p>JPA 生态图</p>
<p>JPA 有三个核心部分ORM 映射元数据、操作实体对象 API 和面向对象的查询语言JPQL。这与 Hibernate 的核心功能基本类似,就不再重复讲述。</p>
<p>Java 开发者应该都知道“Spring 全家桶”的强大Spring 目前已经成为事实上的标准了,很少有企业会完全离开 Spring 来开发 Java 程序。现在的 Spring 已经不仅仅是最早的 IoC 容器了,而是整个 Spring 生态例如Spring Cloud、Spring Boot、Spring Security 等,其中就包含了 Spring Data。</p>
<p><strong>Spring Data 是 Spring 在持久化方面做的一系列扩展和整合</strong>,下图就展示了 Spring Data 中的子项目:</p>
<p><img src="assets/CgqCHmAJKq6AZEwjAAHdDlc6RI0325.png" alt="Drawing 3.png" /></p>
<p><img src="assets/CgqCHmAJKq6AZEwjAAHdDlc6RI0325.png" alt="png" /></p>
<p>Spring Data 生态图</p>
<p>Spring Data 中的每个子项目都对应一个持久化存储通过不断的整合接入各种持久化存储的能力Spring 的生态又向前迈进了一大步,其中最常被大家用到的应该就是 Spring Data JPA。</p>
<p><strong>Spring Data JPA 是符合 JPA 规范的一个 Repository 层的实现</strong>,其所在的位置如下图所示:</p>
<p><img src="assets/CgqCHmAJKraAbIoyAAEm9GmJgx4010.png" alt="Drawing 4.png" /></p>
<p><img src="assets/CgqCHmAJKraAbIoyAAEm9GmJgx4010.png" alt="png" /></p>
<p>Spring Data JPA 生态图</p>
<p>虽然市面上的绝大多数 ORM 框架都实现了 JPA 规范,但是它们在 JPA 基础上也有各自的发展和修改,这样导致我们在使用 JPA 的时候,依旧无法无缝切换底层的 ORM 框架实现。而使用 Spring Data JPA 时,由于<strong>Spring Data JPA 帮助我们抹平了各个 ORM 框架的差异,从而可以让我们的上层业务无缝地切换 ORM 实现框架</strong></p>
<h3>MyBatis</h3>

View File

@@ -203,7 +203,7 @@ function hide_canvas() {
<h3>domain 设计</h3>
<p>在业务系统的开发中,<strong>domain 层的主要目的就是将业务上的概念抽象成面向对象模型中的类</strong>,这些类是业务系统运作的基础。在我们的简易订单系统中,有用户、地址、订单、订单条目和商品这五个核心的概念。</p>
<p>订单系统中 domain 层的设计,如下图所示:</p>
<p><img src="assets/CgqCHmAJK4eAXeXQAACJoVb_GUk600.png" alt="Drawing 0.png" /></p>
<p><img src="assets/CgqCHmAJK4eAXeXQAACJoVb_GUk600.png" alt="png" /></p>
<p>简易订单系统 domain 层设计图</p>
<p>在上图中,<strong>Customer 类抽象的是电商平台中的用户</strong>其中记录了用户的唯一标识id 字段、姓名name 字段以及手机号phone 字段),另外,还记录了当前用户添加的全部送货地址。</p>
<p><strong>Address 类抽象了用户的送货地址</strong>其中记录了街道street 字段、城市city 字段、国家country 字段)等信息,还维护了一个 Customer 类型的引用,指向所属的用户。</p>
@@ -220,7 +220,7 @@ function hide_canvas() {
</ul>
<h3>数据库表设计</h3>
<p>介绍完 domain 层的设计,下面我们再来看对应的数据库表设计,如下图所示:</p>
<p><img src="assets/CgqCHmAJK5mAIH-vAAB6RUBTHlw421.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHmAJK5mAIH-vAAB6RUBTHlw421.png" alt="png" /></p>
<p>简易订单系统数据库表设计</p>
<p>与前面的domain 层设计图相比,其中的各项是可以一一对应起来的。</p>
<ul>

View File

@@ -168,7 +168,7 @@ function hide_canvas() {
<p><strong>安装并启动一个关系型数据是调试 MyBatis 源码的基础</strong>。目前很多互联网公司都将 MySQL 作为首选数据库,所以这里我也就选用 MySQL 数据库来配合调试 MyBatis 源码。</p>
<h4>1. 下载 MySQL</h4>
<p>首先,从 <a href="https://dev.mysql.com/downloads/mysql/">MySQL 官网</a>下载最新版本的 MySQL Community Server。MySQL Community Server 是社区版本的 MySQL 服务端,可以免费试用。这里我选择使用 tar.gz 的方式进行安装,所以需要下载对应的 tar.gz 安装包,如下图红框所示:</p>
<p><img src="assets/CgqCHmAJMb6AeZu1AAK2YtgNDxQ405.png" alt="Drawing 0.png" /></p>
<p><img src="assets/CgqCHmAJMb6AeZu1AAK2YtgNDxQ405.png" alt="png" /></p>
<p>MySQL 下载界面</p>
<h4>2. 配置 MySQL</h4>
<p>下载完 tar.gz 安装包后,我执行如下命令,就可以解压缩该 tar.gz 包,得到 mysql-8.0.22-macos10.15-x86_64 目录。</p>
@@ -181,7 +181,7 @@ function hide_canvas() {
<pre><code>vim mysql.server
</code></pre>
<p>这里我需要将 basedir 和 datadir 变量分别设置为 MySQL 所在根目录以及 MySQL 目录下的 data 目录(如下图所示),最后再执行 :wq 命令保存 mysql.server 的修改并退出。</p>
<p><img src="assets/Ciqc1GAJMc6AdFvAAAYzLYzkW_0254.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Ciqc1GAJMc6AdFvAAAYzLYzkW_0254.png" alt="png" /></p>
<p>mysql.server 文件修改示例图</p>
<h4>3. 启动 MySQL</h4>
<p>随后,我执行了如下命令,进入 MySQL 的 bin 目录:</p>
@@ -191,7 +191,7 @@ function hide_canvas() {
<pre><code>./mysqld --initialize --user=root --basedir=/Users/xxx/Downloads/mysql-8.0.22-macos10.15-x86_64 --datadir=/Users/xxx/Downloads/mysql-8.0.22-macos10.15-x86_64/data
</code></pre>
<p>正常完成初始化过程之后,就可以在命令行中得到 MySQL 的初始默认密码,如下图所示:</p>
<p><img src="assets/Ciqc1GAJMduAfkY2AAPds3lRaC8418.png" alt="Drawing 2.png" /></p>
<p><img src="assets/Ciqc1GAJMduAfkY2AAPds3lRaC8418.png" alt="png" /></p>
<p>成功初始化 MySQL 示例图</p>
<p>通过该默认密码,我就可以启动并登录 MySQL 服务了,首先需要跳转到 support-files 目录中:</p>
<pre><code>cd ../support-files/
@@ -200,7 +200,7 @@ function hide_canvas() {
<pre><code>./mysql.server start
</code></pre>
<p>MySQL 服务正常启动之后,就可以看到如下图所示的输出:</p>
<p><img src="assets/Cip5yGAJMfmAf335AAC2sfPp2nA814.png" alt="Drawing 3.png" /></p>
<p><img src="assets/Cip5yGAJMfmAf335AAC2sfPp2nA814.png" alt="png" /></p>
<p>成功启动 MySQL 示例图</p>
<h4>4. 登录 MySQL</h4>
<p>接下来跳转到 bin 目录:</p>
@@ -210,7 +210,7 @@ function hide_canvas() {
<pre><code>./mysql -uroot -p'rAUhw9e&amp;VPCs'
</code></pre>
<p>登录之后即可进入 MySQL Shell 中,如下图所示:</p>
<p><img src="assets/Cip5yGAJMgWAJCDCAAIMcPd-JbI377.png" alt="Drawing 4.png" /></p>
<p><img src="assets/Cip5yGAJMgWAJCDCAAIMcPd-JbI377.png" alt="png" /></p>
<p>成功登录 MySQL 示例图</p>
<p>然后我就可以在 MySQL Shell 中修改密码,具体命令如下所示:</p>
<pre><code>ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
@@ -221,7 +221,7 @@ function hide_canvas() {
./mysql.server stop
</code></pre>
<p>得到如下输出,即表示 MySQL 服务成功关闭:</p>
<p><img src="assets/Cip5yGAJMgyAXZ2fAAB3LJ1AyKE309.png" alt="Drawing 5.png" /></p>
<p><img src="assets/Cip5yGAJMgyAXZ2fAAB3LJ1AyKE309.png" alt="png" /></p>
<p>成功关闭 MySQL 示例图</p>
<p>这里还需要说明的是,在实际开发过程中,一般会使用到 MySQL 的图形界面客户端,例如 Navicat、MySQL Workbench Community Edition 等,一般只会在线上机器的 Linux 命令行中,才会直接使用 MySQL Shell 执行一些操作。</p>
<p>当然,我个人也很推荐你使用这些图形界面客户端,它可以提高你日常的开发效率。</p>
@@ -232,7 +232,7 @@ function hide_canvas() {
<pre><code>git clone https://github.com/mybatis/mybatis-3.git
</code></pre>
<p>网速不同,这个下载过程的耗时也会有所不同。下载完成后,可得到如下输出:</p>
<p><img src="assets/Ciqc1GAJMhSAKlYfAAGPfec4aLQ116.png" alt="Drawing 6.png" /></p>
<p><img src="assets/Ciqc1GAJMhSAKlYfAAGPfec4aLQ116.png" alt="png" /></p>
<p>MyBatis 下载示例图</p>
<p>此时,在本地我就得到了一个 mybatis-3 目录,执行如下 cd 命令即可进入该目录:</p>
<pre><code>cd ./mybatis-3/
@@ -244,18 +244,18 @@ function hide_canvas() {
<pre><code>git branch -vv
</code></pre>
<p>这里我得到了如下图所示的输出,这表示我已经切换到了 mybatis-3.5.6 这个 tag 上了。</p>
<p><img src="assets/Ciqc1GAJMh-AA0gYAAEnuAcnHRw585.png" alt="Drawing 7.png" /></p>
<p><img src="assets/Ciqc1GAJMh-AA0gYAAEnuAcnHRw585.png" alt="png" /></p>
<p>git 分支示例图</p>
<p>最后,我打开 IDEA ,选择 Open or Import导入 MyBatis 源码,如下图所示:</p>
<p><img src="assets/Cip5yGAJMiiACTxtAAFCLhvMfwQ983.png" alt="Drawing 8.png" /></p>
<p><img src="assets/Cip5yGAJMiiACTxtAAFCLhvMfwQ983.png" alt="png" /></p>
<p>IDEA 导入选项图</p>
<p>导入完成之后,就可以看到 MyBatis 的源码结构,如下图所示:</p>
<p><img src="assets/Cip5yGAJMjGANU2TAAHo4sj86f8952.png" alt="Drawing 9.png" /></p>
<p><img src="assets/Cip5yGAJMjGANU2TAAHo4sj86f8952.png" alt="png" /></p>
<p>MyBatis 的源码结构图</p>
<h3>MyBatis 架构简介</h3>
<p>完成 MyBatis 源码环境搭建之后,我再来带你分析一下 MyBatis 的架构。</p>
<p>MyBatis 分为三层架构,分别是<strong>基础支撑层、核心处理层</strong><strong>接口层</strong>,如下图所示:</p>
<p><img src="assets/CgpVE2AT9G2AXu4RAAM4svUMBPc909.png" alt="Lark20210129-194050.png" /></p>
<p><img src="assets/CgpVE2AT9G2AXu4RAAM4svUMBPc909.png" alt="png" /></p>
<p>MyBatis 三层架构图</p>
<h4>1. 基础支撑层</h4>
<p><strong>基础支撑层是整个 MyBatis 框架的地基,为整个 MyBatis 框架提供了非常基础的功能</strong>其中每个模块都提供了一个内聚的、单一的能力MyBatis 基础支撑层按照这些单一的能力可以划分为上图所示的九个基础模块。</p>
@@ -267,7 +267,7 @@ function hide_canvas() {
<li>在将 ResultSet 映射成结果对象的时候,类型转换模块会将 JDBC 类型数据转换成 Java 类型数据。</li>
</ul>
<p>具体情况如下图所示:</p>
<p><img src="assets/Cip5yGAT9HeAabOAAACw3SAaflI907.png" alt="Lark20210129-194053.png" /></p>
<p><img src="assets/Cip5yGAT9HeAabOAAACw3SAaflI907.png" alt="png" /></p>
<p>类型转换基本功能示意图</p>
<p><strong>第二个,日志模块。</strong> 日志是我们生产实践中排查问题、定位 Bug、锁定性能瓶颈的主要线索来源在任何一个成熟系统中都会有级别合理、信息翔实的日志模块MyBatis 也不例外。MyBatis 提供了日志模块来集成 Java 生态中的第三方日志框架,该模块目前可以集成 Log4j、Log4j2、slf4j 等优秀的日志框架。</p>
<p><strong>第三个,反射工具模块。</strong> Java 中的反射功能非常强大,许多开源框架都会依赖反射实现一些相对灵活的需求,但是大多数 Java 程序员在实际工作中很少会直接使用到反射技术。MyBatis 的反射工具箱是在 Java 反射的基础之上进行的一层封装,为上层使用方提供更加灵活、方便的 API 接口,同时缓存 Java 的原生反射相关的元数据,提升了反射代码执行的效率,优化了反射操作的性能。</p>
@@ -275,7 +275,7 @@ function hide_canvas() {
<p>这里特别说明的是,在使用 MyBatis 的时候,我们无须编写 Mapper 接口的具体实现,而是利用 Binding 模块自动生成 Mapper 接口的动态代理对象。有些简单的数据操作,我们还可以直接在 Mapper 接口中使用注解完成,连 Mapper.xml 配置文件都无须编写,但如果 ResultSet 映射以及动态 SQL 非常复杂,还是建议在 Mapper.xml 配置文件中维护会比较方便。</p>
<p><strong>第五个,数据源模块。</strong> 持久层框架核心组件之一就是数据源一款性能出众的数据源可以成倍提升系统的性能。MyBatis 自身提供了一套不错的数据源实现,也是 MyBatis 的默认实现。另外,在 Java 生态中就有很多优异开源的数据源可供选择MyBatis 的数据源模块中也提供了与第三方数据源集成的相关接口,这也为用户提供了更多的选择空间,提升了数据源切换的灵活性。</p>
<p><strong>第六个,缓存模块。</strong> 数据库是实践生成中非常核心的存储,很多业务数据都会落地到数据库,所以数据库性能的优劣直接影响了上层业务系统的优劣。我们很多线上业务都是读多写少的场景,在数据库遇到瓶颈时,缓存是最有效、最常用的手段之一(如下图所示),正确使用缓存可以将一部分数据库请求拦截在缓存这一层,这就能够减少一部分数据库的压力,提高系统性能。</p>
<p><img src="assets/Cip5yGAT9ICAItLcAAHSeuL0ugo137.png" alt="Lark20210129-194055.png" /></p>
<p><img src="assets/Cip5yGAT9ICAItLcAAHSeuL0ugo137.png" alt="png" /></p>
<p>缓存模块结构图</p>
<p>除了使用 Redis、Memcached 等外置的第三方缓存以外持久化框架一般也会自带内置的缓存例如MyBatis 就提供了一级缓存和二级缓存,具体实现位于基础支撑层的缓存模块中。</p>
<p><strong>第七个,解析器模块</strong>。在上一讲的订单系统示例中,我们可以看到 MyBatis 中有两大部分配置文件需要解析,一个是 mybatis-config.xml 配置文件,另一个是 Mapper.xml 配置文件。这两个文件都是由 MyBatis 的解析器模块进行解析的,其中主要是依赖 XPath 实现 XML 配置文件以及各类表达式的高效解析。</p>
@@ -290,7 +290,7 @@ function hide_canvas() {
<p><strong>第三个SQL 执行。</strong> 在 MyBatis 中,要执行一条 SQL 语句会涉及非常多的组件比较核心的有Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。</p>
<p>其中Executor 会调用事务管理模块实现事务的相关控制同时会通过缓存模块管理一级缓存和二级缓存。SQL 语句的真正执行将会由 StatementHandler 实现。那具体是怎么完成的呢StatementHandler 会先依赖 ParameterHandler 进行 SQL 模板的实参绑定,然后由 java.sql.Statement 对象将 SQL 语句以及绑定好的实参传到数据库执行,从数据库中拿到 ResultSet最后由 ResultSetHandler 将 ResultSet 映射成 Java 对象返回给调用方,这就是 SQL 执行模块的核心。</p>
<p>下图展示了 MyBatis 执行一条 SQL 语句的核心过程:</p>
<p><img src="assets/Cip5yGAT9JKAZRtgAAKpY4hLF6U463.png" alt="Lark20210129-194058.png" /></p>
<p><img src="assets/Cip5yGAT9JKAZRtgAAKpY4hLF6U463.png" alt="png" /></p>
<p>执行 SQL 语句的核心流程图</p>
<p><strong>第四个,插件。</strong> 很多成熟的开源框架,都会以各种方式提供扩展能力。当框架原生能力不能满足某些场景的时候,就可以针对这些场景实现一些插件来满足需求,这样的框架才能有足够的生命力。这也是 MyBatis 插件接口存在的意义。</p>
<p>与此同时,在实际应用的时候,你也可以通过自定义插件来扩展 MyBatis或者改变 MyBatis 的默认行为。因为插件会影响 MyBatis 内核的行为,所以在自定义插件之前,你必须非常了解 MyBatis 内部的运行原理,以避免写出不符合预期的插件,引入一些诡异的功能 Bug 或性能问题。</p>

View File

@@ -223,7 +223,7 @@ public ArrayList getA();
}
</code></pre>
<p>Invoker 接口的继承关系如下图所示:</p>
<p><img src="assets/Cgp9HWAaP06ASpANAACOQ0WzRSk795.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Cgp9HWAaP06ASpANAACOQ0WzRSk795.png" alt="png" /></p>
<p>Invoker 接口继承关系图</p>
<p>其中MethodInvoker 是通过反射方式执行底层封装的 Method 方法例如getter/setter 方法完成属性读写效果的Get/SetFieldInvoker 是通过反射方式读写底层封装的 Field 字段,进而实现属性读写效果的。</p>
<h4>4. ReflectorFactory</h4>
@@ -251,7 +251,7 @@ public ArrayList getA();
<h3>ObjectWrapper</h3>
<p><strong>MetaClass 中封装的是 Class 元信息ObjectWrapper 封装的则是对象元信息</strong>。在 ObjectWrapper 中抽象了一个对象的属性信息,并提供了查询对象属性信息的相关方法,以及更新属性值的相关方法。</p>
<p>ObjectWrapper 的实现类如下图所示:</p>
<p><img src="assets/CioPOWAaP22Aea6TAAB1kkkDx98845.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CioPOWAaP22Aea6TAAB1kkkDx98845.png" alt="png" /></p>
<p>ObjectWrapper 继承关系图</p>
<p><strong>BaseWrapper 是 ObjectWrapper 接口的抽象实现</strong>,其中只有一个 MetaObject 类型的字段。BaseWrapper 为子类实现了 resolveCollection()、getCollectionValue() 和 setCollectionValue() 三个针对集合对象的处理方法。其中resolveCollection() 方法会将指定属性作为集合对象返回,底层依赖 MetaObject.getValue()方法实现后面还会详细介绍。getCollectionValue() 方法和 setCollectionValue() 方法会解析属性表达式的下标信息,然后获取/设置集合中的对应元素,这里解析属性表达式依然是依赖前面介绍的 PropertyTokenizer 工具类。</p>
<p><strong>BeanWrapper 继承了 BaseWrapper 抽象类</strong>,底层除了封装了一个 JavaBean 对象之外,还封装了该 JavaBean 类型对应的 MetaClass 对象,以及从 BaseWrapper 继承下来的 MetaObject 对象。</p>

View File

@@ -164,11 +164,11 @@ function hide_canvas() {
<p id="tip" align="center"></p>
<div><h1>05 数据库类型体系与 Java 类型体系之间的“爱恨情仇”</h1>
<p>作为一个 Java 程序员,你应该已经具备了使用 JDBC 操作数据库的基础技能。在使用 JDBC 的时候,你会发现 JDBC 的数据类型与 Java 语言中的数据类型虽然有点对应关系,如下图所示,但还是无法做到一一对应,也自然无法做到自动映射。</p>
<p><img src="assets/Cgp9HWAeMiSAcga0AAEpsa9onlg651.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Cgp9HWAeMiSAcga0AAEpsa9onlg651.png" alt="png" /></p>
<p>数据库类型与 Java 类型对应图表</p>
<p>在使用 PreparedStatement 执行 SQL 语句之前,都是需要手动调用 setInt()、setString() 等 set 方法绑定参数,这不仅仅是告诉 JDBC 一个 SQL 模板中哪个占位符需要使用哪个实参,还会将数据从 Java 类型转换成 JDBC 类型。当从 ResultSet 中获取数据的时候,则是一个逆过程,数据会从 JDBC 类型转换为 Java 类型。</p>
<p>可以使用 MyBatis 中的<strong>类型转换器</strong>,完成上述两次类型转换,如下图所示:</p>
<p><img src="assets/CioPOWAeMi6AdTRAAAENMX_HsyU054.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CioPOWAeMi6AdTRAAAENMX_HsyU054.png" alt="png" /></p>
<p>JDBC 类型数据与 Java 类型数据转换示意图</p>
<h3>深入 TypeHandler</h3>
<p>说了这么多,类型转换器到底是怎么定义的呢?其实,<strong>MyBatis 中的类型转换器就是 TypeHandler 这个接口</strong>,其定义如下:</p>
@@ -182,7 +182,7 @@ function hide_canvas() {
}
</code></pre>
<p><strong>MyBatis 中定义了 BaseTypeHandler 抽象类来实现一些 TypeHandler 的公共逻辑</strong>BaseTypeHandler 在实现 TypeHandler 的同时,还实现了 TypeReference 抽象类。其继承关系如下图所示:</p>
<p><img src="assets/CioPOWAeMkCANy6LAABJPBfXPJY527.png" alt="Drawing 2.png" /></p>
<p><img src="assets/CioPOWAeMkCANy6LAABJPBfXPJY527.png" alt="png" /></p>
<p>TypeHandler 继承关系图</p>
<p>在 BaseTypeHandler 中,简单实现了 TypeHandler 接口的 setParameter() 方法和 getResult() 方法。</p>
<ul>
@@ -191,7 +191,7 @@ function hide_canvas() {
</ul>
<p>BaseTypeHandler 的具体实现比较简单,这里就不再展示,你若感兴趣的话可以参考<a href="https://github.com/xxxlxy2008/mybatis">源码</a>进行学习。</p>
<p>下图展示了 BaseTypeHandler 的全部实现类,虽然实现类比较多,但是它们的实现方式大同小异。</p>
<p><img src="assets/Cgp9HWAeMkuAI22uAApwhcDLfQ4596.png" alt="Drawing 3.png" /></p>
<p><img src="assets/Cgp9HWAeMkuAI22uAApwhcDLfQ4596.png" alt="png" /></p>
<p>BaseTypeHandler 实现类示意图</p>
<p>这里我们以 LongTypeHandler 为例进行分析,具体实现如下:</p>
<pre><code>public class LongTypeHandler extends BaseTypeHandler&lt;Long&gt; {

View File

@@ -168,7 +168,7 @@ function hide_canvas() {
<h3>适配器模式</h3>
<p>适配器模式主要解决的是<strong>由于接口不能兼容而导致类无法使用的问题,这在处理遗留代码以及集成第三方框架的时候用得比较多</strong>。其核心原理是:<strong>通过组合的方式,将需要适配的类转换成使用者能够使用的接口</strong></p>
<p>适配器模式的类图如下所示:</p>
<p><img src="assets/Cgp9HWAfYoOAKO6lAAEyIgsMVKA161.png" alt="2.png" /></p>
<p><img src="assets/Cgp9HWAfYoOAKO6lAAEyIgsMVKA161.png" alt="png" /></p>
<p>适配器模式类图</p>
<p>在该类图中,你可以看到适配器模式涉及的三个核心角色。</p>
<ul>
@@ -181,7 +181,7 @@ function hide_canvas() {
<h3>日志模块</h3>
<p><strong>MyBatis 自定义的 Log 接口位于 org.apache.ibatis.logging 包中,相关的适配器也位于该包中</strong>,下面我们就来看看该模块的具体实现。</p>
<p>首先是 LogFactory 工厂类,它负责创建 Log 对象。这些 Log 接口的实现类中,就包含了多种第三方日志框架的适配器,如下图所示:</p>
<p><img src="assets/CioPOWAfYo6AbKZWAAKqpRwXpuA169.png" alt="3.png" /></p>
<p><img src="assets/CioPOWAfYo6AbKZWAAKqpRwXpuA169.png" alt="png" /></p>
<p>Log 接口继承关系图</p>
<p>在 LogFactory 类中有<a href="https://github.com/xxxlxy2008/mybatis/blob/master/src/main/java/org/apache/ibatis/logging/LogFactory.java#L31-L43">一段静态代码块</a>,其中会依次加载各个第三方日志框架的适配器。在静态代码块执行的 tryImplementation() 方法中,首先会检测 logConstructor 字段是否为空,如果不为空,则表示已经成功确定当前使用的日志框架,直接返回;如果为空,则在当前线程中执行传入的 Runnable.run() 方法,尝试确定当前使用的日志框架。</p>
<p>以 JDK Logging 的加载流程useJdkLogging() 方法)为例,其具体代码实现和注释如下:</p>
@@ -203,7 +203,7 @@ private static void setImplementation(Class&lt;? extends Log&gt; implClass) {
</code></pre>
<p>下面我们以 Jdk14LoggingImpl 为例介绍一下 MyBatis Log 接口的实现。</p>
<p>Jdk14LoggingImpl 作为 Java Logging 的适配器,在实现 MyBatis Log 接口的同时,在内部还封装了一个 java.util.logging.Logger 对象(这是 JDK 提供的日志框架),如下图所示:</p>
<p><img src="assets/CioPOWAeM0WAMQPTAABNRPFy7R0954.png" alt="Drawing 2.png" /></p>
<p><img src="assets/CioPOWAeM0WAMQPTAABNRPFy7R0954.png" alt="png" /></p>
<p>Jdk14LoggingImpl 继承关系图</p>
<p>Jdk14LoggingImpl 对 Log 接口的实现也比较简单其中会将日志输出操作委托给底层封装的java.util.logging.Logger 对象的相应方法这与前文介绍的典型适配器模式的实现完全一致。Jdk14LoggingImpl 中的核心实现以及注释如下:</p>
<pre><code>public class Jdk14LoggingImpl implements Log {
@@ -226,7 +226,7 @@ private static void setImplementation(Class&lt;? extends Log&gt; implClass) {
<p>在后面即将介绍的 org.apache.ibatis.logging.jdbc 包中,使用到了 JDK 动态代理的相关知识,所以这里我们就先来介绍一下经典的静态代理模式,以及 JDK 提供的动态代理。</p>
<h4>1. 静态代理模式</h4>
<p>经典的静态代理模式,其类图如下所示:</p>
<p><img src="assets/Cgp9HWAfYrOAWv7JAAD2hkpzuWw664.png" alt="4.png" /></p>
<p><img src="assets/Cgp9HWAfYrOAWv7JAAD2hkpzuWw664.png" alt="png" /></p>
<p>代理模式类图</p>
<p>从该类图中,你可以看到与代理模式相关的三个核心角色。</p>
<ul>
@@ -378,7 +378,7 @@ private static void setImplementation(Class&lt;? extends Log&gt; implClass) {
<h3>JDBC Logger</h3>
<p>了解了代理模式以及 JDK 动态代理的基础知识之后,下面我们开始分析 org.apache.ibatis.logging.jdbc 包中的内容。</p>
<p>首先来看其中<strong>最基础的抽象类—— BaseJdbcLogger它是 jdbc 包下其他 Logger 类的父类</strong>,继承关系如下图所示:</p>
<p><img src="assets/CioPOWAfYsuAc9WjAAFm7izVaMI477.png" alt="1.png" /></p>
<p><img src="assets/CioPOWAfYsuAc9WjAAFm7izVaMI477.png" alt="png" /></p>
<p>BaseJdbcLogger 继承关系图</p>
<p>在 BaseJdbcLogger 这个抽象类中,定义了 SET_METHODS 和 EXECUTE_METHODS 两个 Set 类型的集合。其中SET_METHODS 用于记录绑定 SQL 参数涉及的全部 set*() 方法名称,例如 setString() 方法、setInt() 方法等。EXECUTE_METHODS 用于记录执行 SQL 语句涉及的所有方法名称,例如 execute() 方法、executeUpdate() 方法、executeQuery() 方法、addBatch() 方法等。这两个集合都是在 BaseJdbcLogger 的静态代码块中被填充的。</p>
<p>从上面的 BaseJdbcLogger 继承关系图中可以看到BaseJdbcLogger 的子类同时会实现 InvocationHandler 接口。</p>

View File

@@ -165,13 +165,13 @@ function hide_canvas() {
<div><h1>07 深入数据源和事务,把握持久化框架的两个关键命脉</h1>
<p>数据源是持久层框架中最核心的组件之一,在实际工作中比较常见的数据源有 C3P0、Apache Common DBCP、Proxool 等。作为一款成熟的持久化框架MyBatis 不仅自己提供了一套数据源实现,而且还能够方便地集成第三方数据源。</p>
<p><strong>javax.sql.DataSource 是 Java 语言中用来抽象数据源的接口</strong>其中定义了所有数据源实现的公共行为MyBatis 自身提供的数据源实现也要实现该接口。MyBatis 提供了两种类型的数据源实现,分别是 PooledDataSource 和 UnpooledDataSource继承关系如下图所示</p>
<p><img src="assets/Cgp9HWApSnyAeZddAADG9kv9Y-c887.png" alt="1.png" /></p>
<p><img src="assets/Cgp9HWApSnyAeZddAADG9kv9Y-c887.png" alt="png" /></p>
<p>针对不同的 DataSource 实现MyBatis 提供了不同的工厂实现来进行创建,如下图所示,这是工厂方法模式的一个典型应用场景。</p>
<p><img src="assets/CioPOWApSomAM5hXAADlDsSaiAY054.png" alt="2.png" /></p>
<p><img src="assets/CioPOWApSomAM5hXAADlDsSaiAY054.png" alt="png" /></p>
<p><strong>编写一个设计合理、性能优秀的数据源只是第一步</strong>在通过数据源拿到数据库连接之后还需要开启事务才能进行数据的修改。MyBatis 对数据库事务进行了一层抽象,也就是我们这一讲后面要介绍的 Transaction 接口,它可以<strong>管理事务的开启、提交和回滚</strong></p>
<h3>工厂方法模式</h3>
<p>工厂方法模式中定义了 Factory 这个工厂接口,如下图所示,其中定义了 createProduct() 方法创建右侧继承树中的对象,不同的工厂接口实现类会创建右侧继承树中不同 Product 实现类(例如 ProductImpl 1 和 ProductImpl 2</p>
<p><img src="assets/CioPOWApSqKAQyYyAAD_0kpOQec437.png" alt="3.png" /></p>
<p><img src="assets/CioPOWApSqKAQyYyAAD_0kpOQec437.png" alt="png" /></p>
<p>从上图中,我们可以看到工厂方法模式由四个核心角色构成。</p>
<ul>
<li>Factory 接口:工厂方法模式的核心接口之一。使用方会依赖 Factory 接口创建 Product 对象实例。</li>
@@ -184,7 +184,7 @@ function hide_canvas() {
<h3>数据源工厂</h3>
<p>了解了工厂方法模式的基础知识之后,下面我们回到 MyBatis 的数据源实现上来。<strong>MyBatis 的数据源模块也是用到了工厂方法模式,如果需要扩展新的数据源实现时,只需要添加对应的 Factory 实现类,新的数据源就可以被 MyBatis 使用。</strong></p>
<p>DataSourceFactory 接口就扮演了 MyBatis 数据源实现中的 Factory 接口角色。UnpooledDataSourceFactory 和 PooledDataSourceFactory 实现了 DataSourceFactory 接口,也就是 Factory 接口实现类的角色。三者的继承关系如下图所示:</p>
<p><img src="assets/Cgp9HWApSreAMsSEAADxE9_08B0637.png" alt="5.png" /></p>
<p><img src="assets/Cgp9HWApSreAMsSEAADxE9_08B0637.png" alt="png" /></p>
<p>DataSourceFactory 接口中最核心的方法是 getDataSource() 方法,该方法用来生成一个 DataSource 对象。</p>
<p>在 UnpooledDataSourceFactory 这个实现类的初始化过程中,会直接创建 UnpooledDataSource 对象,其中的 dataSource 字段会指向该 UnpooledDataSource 对象。接下来调用的 setProperties() 方法会根据传入的配置信息,完成对该 UnpooledDataSource 对象相关属性的设置。</p>
<p>UnpooledDataSourceFactory 对于 getDataSource() 方法的实现就相对简单了,其中直接返回了上面创建的 UnpooledDataSource 对象。</p>
@@ -469,7 +469,7 @@ function hide_canvas() {
<p>可见,<strong>控制事务在一个以数据库为基础的服务中,是一件非常重要的工作</strong>。为此MyBatis 专门抽象出来一个 Transaction 接口,好在相较于我们上面讲述的数据源,这部分内容还是比较简单、比较好理解的。</p>
<p><strong>Transaction 接口是 MyBatis 中对数据库事务的抽象</strong>,其中定义了<strong>提交事务、回滚事务</strong>,以及<strong>获取事务底层数据库连接</strong>的方法。</p>
<p>JdbcTransaction、ManagedTransaction 是 MyBatis 自带的两个 Transaction 接口实现,这里也使用到了工厂方法模式,如下图所示:</p>
<p><img src="assets/CioPOWApSyGAb7OzAAFWzMbS1T8710.png" alt="6.png" /></p>
<p><img src="assets/CioPOWApSyGAb7OzAAFWzMbS1T8710.png" alt="png" /></p>
<p><strong>TransactionFactory 是用于创建 Transaction 的工厂接口</strong>,其中最核心的方法是 newTransaction() 方法,它会根据数据库连接或数据源创建 Transaction 对象。</p>
<p>JdbcTransactionFactory 和 ManagedTransactionFactory 是 TransactionFactory 的两个实现类,分别用来创建 JdbcTransaction 对象和 ManagedTransaction 对象,具体实现比较简单,这里就不再展示,你若感兴趣的话可以参考<a href="https://github.com/xxxlxy2008/mybatis">源码</a>进行学习。</p>
<p>接下来,我们看一下 JdbcTransaction 的实现,其中维护了事务关联的数据库连接以及数据源对象,同时还记录了事务自身的属性,例如,事务隔离级别和是否自动提交。</p>

View File

@@ -175,7 +175,7 @@ function hide_canvas() {
<p><strong>装饰器模式就是一种通过组合方式实现扩展的设计模式</strong>,它可以完美地解决上述功能增强的问题。装饰器的核心思想是为已有实现类创建多个包装类,由这些新增的包装类完成新需求的扩展。</p>
<p><strong>装饰器模式使用的是组合方式,相较于继承这种静态的扩展方式,装饰器模式可以在运行时根据系统状态,动态决定为一个实现类添加哪些扩展功能。</strong></p>
<p>装饰器模式的核心类图,如下所示:</p>
<p><img src="assets/Cgp9HWAwwdGAWEZ4AAG1zgT1MQM431.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Cgp9HWAwwdGAWEZ4AAG1zgT1MQM431.png" alt="png" /></p>
<p>装饰器模式类图</p>
<p>从图中可以看到,装饰器模式中的核心类主要有下面四个。</p>
<ul>
@@ -232,7 +232,7 @@ function hide_canvas() {
<p>在 releaseLock() 方法中,会从 locks 集合中删除 Key 关联的 CountDownLatch 对象,并唤醒阻塞在这个 CountDownLatch 对象上的业务线程。</p>
<p>看到这里,你可能会问:假设业务线程 1、2 并发访问某个 Key线程 1 查询 delegate 缓存失败不释放锁timeout &lt;=0 的时候,线程 2 就会阻塞吗?是的,但是线程 2 不会永久阻塞,因为我们需要保证线程 1 接下来会查询数据库,并调用 putObject() 方法或 removeObject() 方法,其中会通过 releaseLock() 方法释放锁。</p>
<p>最终,我们得到 BlockingCache 的核心原理如下图所示:</p>
<p><img src="assets/CioPOWAwwimAGb51AAJd328lR7k035.png" alt="Drawing 4.png" /></p>
<p><img src="assets/CioPOWAwwimAGb51AAJd328lR7k035.png" alt="png" /></p>
<p>BlockingCache 核心原理图</p>
<h4>2. FifoCache</h4>
<p>MyBatis 中的缓存本质上就是 JVM 堆中的一块内存,我们需要严格控制 Cache 的大小,防止 Cache 占用内存过大而影响程序的性能。操作系统有很多缓存淘汰规则MyBatis 也提供了类似的规则来清理缓存。</p>

View File

@@ -170,7 +170,7 @@ function hide_canvas() {
<h3>构造者模式</h3>
<p>构造者模式最核心的思想就是将创建复杂对象的过程与复杂对象本身进行拆分。通俗来讲,构造者模式是<strong>将复杂对象的创建过程分解成了多个简单步骤,在创建复杂对象的时候,只需要了解复杂对象的基本属性即可,而不需要关心复杂对象的内部构造过程</strong>。这样的话,使用方只需要关心这个复杂对象要什么数据,而不再关心内部细节。</p>
<p>构造者模式的类图如下所示:</p>
<p><img src="assets/Cgp9HWA01CyAP_FuAAGR6B2VRBg565.png" alt="2021223-18655.png" /></p>
<p><img src="assets/Cgp9HWA01CyAP_FuAAGR6B2VRBg565.png" alt="png" /></p>
<p>构造者模式类图</p>
<p>从图中,我们可以看到构造者模式的四个核心组件。</p>
<ul>
@@ -185,7 +185,7 @@ function hide_canvas() {
<p>介绍完构造者模式相关的知识点之后,下面我们正式开始介绍 MyBatis 的初始化过程。</p>
<p><strong>MyBatis 初始化的第一个步骤就是加载和解析 mybatis-config.xml 这个全局配置文件</strong>,入口是 XMLConfigBuilder 这个 Builder 对象,它由 SqlSessionFactoryBuilder.build() 方法创建。XMLConfigBuilder 会解析 mybatis-config.xml 配置文件得到对应的 Configuration 全局配置对象,然后 SqlSessionFactoryBuilder 会根据得到的 Configuration 全局配置对象创建一个 DefaultSqlSessionFactory 对象返回给上层使用。</p>
<p>这里<strong>创建的 XMLConfigBuilder 对象的核心功能就是解析 mybatis-config.xml 配置文件</strong>。XMLConfigBuilder 有一部分能力继承自 BaseBuilder 抽象类,具体继承关系如下图所示:</p>
<p><img src="assets/Cgp9HWA01DeAFFn1AAEKQNyimxk937.png" alt="2021223-1877.png" /></p>
<p><img src="assets/Cgp9HWA01DeAFFn1AAEKQNyimxk937.png" alt="png" /></p>
<p>BaseBuilder 继承关系图</p>
<p>BaseBuilder 抽象类扮演了构造者模式中 Builder 接口的角色,下面我们先来看 BaseBuilder 中各个字段的定义。</p>
<ul>
@@ -373,7 +373,7 @@ if (context != null) {
}
</code></pre>
<p>可以看到,解析<code>&lt;databaseIdProvider&gt;</code> 标签之后会得到一个 DatabaseIdProvider 对象,其核心方法是 getDatabaseId() 方法,主要是根据前面解析得到的 DataSource 对象来生成 DatabaseId。DatabaseIdProvider 的继承关系如下图所示:</p>
<p><img src="assets/CioPOWA01E6AM0S_AAFq9Ci2CSc510.png" alt="2021223-1874.png" /></p>
<p><img src="assets/CioPOWA01E6AM0S_AAFq9Ci2CSc510.png" alt="png" /></p>
<p>DatabaseIdProvider 继承关系图</p>
<p>从继承关系图中可以看出DefaultDatabaseIdProvider 是个空实现,而且已被标记为过时了,所以这里我们就重点来看 VendorDatabaseIdProvider 实现。</p>
<p>在 getDatabaseId() 方法中VendorDatabaseIdProvider 首先会从 DataSource 中拿到数据库的名称,然后根据 <code>&lt;databaseIdProvider&gt; </code>标签配置和 DataSource 返回的数据库名称,确定最终的 DatabaseId 标识,具体实现如下:</p>

View File

@@ -271,7 +271,7 @@ function hide_canvas() {
}
</code></pre>
<p>MyBatis 为 SqlNode 接口提供了非常多的实现类(如下图),其中很多实现类都对应一个动态 SQL 标签,但是也有 SqlNode 实现扮演了组合模式中 Composite 的角色例如MixedSqlNode 实现类。</p>
<p><img src="assets/Cgp9HWA-CCGAMA5bAADLCPKFfWg640.png" alt="image.png" /></p>
<p><img src="assets/Cgp9HWA-CCGAMA5bAADLCPKFfWg640.png" alt="png" /></p>
<p>SqlNode 继承关系图</p>
<p>下面我们就来逐一介绍这每个 SqlNode 实现类的功能和核心实现。</p>
<h4>1. StaticTextSqlNode 和 MixedSqlNode</h4>

View File

@@ -192,7 +192,7 @@ private void applyItem(DynamicContext context, Object o, int i) {
</code></pre>
<p>但如果传入的集合不是 Map 类型,则通过 applyIndex() 方法和 applyItem() 方法将集合元素的下标索引和元素值本身绑定到 PrefixedContext 中。</p>
<p>完成 PrefixedContext 的绑定之后,会调用 <code>&lt;foreach&gt;</code> 标签下子 SqlNode 的 apply() 方法,其中传入的 DynamicContext 实际上是 ForEachSqlNode$FilteredDynamicContext 这个内部类,它也是 DynamicContext 的装饰器,核心功能是:根据前面在 PrefixedContext 中绑定的各种变量,处理 SQL 片段中的“#{}”占位符。FilteredDynamicContext 在多次循环中的处理效果如下图所示:</p>
<p><img src="assets/Cgp9HWBB-4WADZbeAAIPDjI-G2A404.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Cgp9HWBB-4WADZbeAAIPDjI-G2A404.png" alt="png" /></p>
<p>FilteredDynamicContext 变化过程示意图</p>
<p>下面是 FilteredDynamicContext.appendSql() 方法的核心实现:</p>
<pre><code>public void appendSql(String sql) {
@@ -244,7 +244,7 @@ private void applyItem(DynamicContext context, Object o, int i) {
<p>经过上述一系列处理之后SQL 语句最终会由 SqlSource 进行最后的处理。</p>
<p><strong>在 SqlSource 接口中只定义了一个 getBoundSql() 方法,它控制着动态 SQL 语句解析的整个流程</strong>,它会根据从 Mapper.xml 映射文件(或注解)解析到的 SQL 语句以及执行 SQL 时传入的实参,返回一条可执行的 SQL。</p>
<p>下图展示了 SqlSource 接口的核心实现:</p>
<p><img src="assets/CioPOWBB-6CAFZlRAAEeTrexVm4358.png" alt="Drawing 3.png" /></p>
<p><img src="assets/CioPOWBB-6CAFZlRAAEeTrexVm4358.png" alt="png" /></p>
<p>SqlSource 接口继承图</p>
<p>下面我们简单介绍一下这三个核心实现类的具体含义。</p>
<ul>

View File

@@ -253,7 +253,7 @@ function hide_canvas() {
<h4>2. 确定 ResultMap</h4>
<p>在完成 ResultSet 的预处理之后,接下来会<strong>通过 resolveDiscriminatedResultMap() 方法处理 <discriminator> 标签,确定此次映射操作最终使用的 ResultMap 对象</strong></p>
<p>为了更加方便和完整地描述 resolveDiscriminatedResultMap() 方法的核心流程,这里我们结合一个简单示例进行分析,比如,现在有一个 ResultSet 包含 id、name、classify、subClassify 四列,并且由 animalMap 来映射该 ResultSet具体如下图所示</p>
<p><img src="assets/CioPOWBINOWAMLKdAAg_Jy5Lhxg738.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CioPOWBINOWAMLKdAAg_Jy5Lhxg738.png" alt="png" /></p>
<p>&lt; discriminator&gt;处理示例图</p>
<p>通过 resolveDiscriminatedResultMap() 方法确定 ResultMap 的流程大致是这样的:</p>
<ul>

View File

@@ -165,7 +165,7 @@ function hide_canvas() {
<div><h1>16 StatementHandler参数绑定、SQL 执行和结果映射的奠基者</h1>
<p><strong>StatementHandler 接口是 MyBatis 中非常重要的一个接口</strong>,其实现类完成 SQL 语句执行中最核心的一系列操作,这也是后面我们要介绍的 Executor 接口实现的基础。</p>
<p>StatementHandler 接口的定义如下图所示:</p>
<p><img src="assets/Cgp9HWBRyPSAPnqwAADa0tXnYqQ008.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Cgp9HWBRyPSAPnqwAADa0tXnYqQ008.png" alt="png" /></p>
<p>StatementHandler 接口中定义的方法</p>
<p>可以看到,其中提供了创建 Statement 对象prepare() 方法)、为 SQL 语句绑定实参parameterize() 方法)、执行单条 SQL 语句query() 方法和 update() 方法)、批量执行 SQL 语句batch() 方法)等多种功能。</p>
<p>下图展示了 MyBatis 中提供的所有 StatementHandler 接口实现类,以及它们的继承关系:</p>

View File

@@ -186,7 +186,7 @@ function hide_canvas() {
<p>默认情况下,<strong>我们在使用 MyBatis 的时候用的都是 DefaultSqlSession 这个默认的 SqlSession 实现</strong>。DefaultSqlSession 中维护了一个 Executor 对象通过它来完成数据库操作以及事务管理。DefaultSqlSession 在选择使用哪种 Executor 实现的时候使用到了策略模式DefaultSqlSession 扮演了策略模式中的 StrategyUser 角色Executor 接口扮演的是 Strategy 角色Executor 接口的不同实现则对应 StrategyImpl 的角色。</p>
<p>另外DefaultSqlSession 还维护了一个 dirty 字段来标识缓存中是否有脏数据,它在执行 update() 方法修改数据时会被设置为 true并在后续参与事务控制决定当前事务是否需要提交或回滚。</p>
<p>下面接着来看 DefaultSqlSession 对 SqlSession 接口的实现。DefaultSqlSession 为每一类数据操作方法提供了多个重载,尤其是 select*() 操作,而且这些 select*() 方法的重载之间有相互依赖的关系,如下图所示:</p>
<p><img src="assets/Cgp9HWBYb-iAKkKeAADz5INxXLw311.png" alt="Drawing 2.png" /></p>
<p><img src="assets/Cgp9HWBYb-iAKkKeAADz5INxXLw311.png" alt="png" /></p>
<p>select() 方法之间的调用关系</p>
<p>通过上图我们可以清晰地看到,所有 select*() 方法最终都是通过调用 Executor.query() 方法执行 select 语句、完成数据查询操作的,之所以有不同的 select*() 重载,主要是对结果对象的需求不同。例如,我们使用 selectList() 重载时,希望返回的结果对象是一个 List集合使用 selectMap() 重载时,希望查询到的结果集被转换成 Map 类型集合返回至于select() 重载,则会由 ResultHandler 来处理结果对象。</p>
<p>DefaultSqlSession 中的 insert()、update()、delete() 等修改数据的方法以及 commit()、rollback() 等事务管理的方法同样也有多个重载它们最终也是委托到Executor 中的同名方法,完成数据修改操作以及事务管理操作的。</p>

View File

@@ -227,7 +227,7 @@ CREATE TABLE `t_customer` ( # 创建t_customer表
<pre><code>java -jar mybatis-generator-core-1.4.0.jar -configfile generatorConfig.xml
</code></pre>
<p>命令正常执行完成之后,可以看到 src 目录下生成的文件如下图所示:</p>
<p><img src="assets/CioPOWBtTDqAYagkAABmeFv2Z84519.png" alt="Drawing 0.png" /></p>
<p><img src="assets/CioPOWBtTDqAYagkAABmeFv2Z84519.png" alt="png" /></p>
<p>MyBatis-Generator 工具类生成结果图</p>
<p>生成的 Customer.java 类是一个 Model 类(或者说 Domain 类),包含了 id、name、password、account 属性CustomerMapper.xml 是 Customer 对应的 Mapper.xml 配置文件,其中定义了按照 id 进行查询和删除的 select、delete 语句,以及全字段写入和更新的 insert、update 语句CustomerMapper 接口中包含了与 CustomerMapper.xml 对应的方法。该示例中生成的代码并不复杂,在你生成代码之后,也希望你能够自己分析一下。</p>
<h3>MyBatis 分页插件</h3>
@@ -268,7 +268,7 @@ if (!dialect.skip(ms, parameter, rowBounds)) {
return dialect.afterPage(resultList, parameter, rowBounds);
</code></pre>
<p>通过对 PageInterceptor 的分析我们看到,<strong>核心的分页逻辑都是在 Dialect 中完成的</strong>PageHelper 针对每个数据库都提供了一个 Dialect 接口实现。下图展示了 MySQL 数据库对应的 Dialect 接口实现:</p>
<p><img src="assets/Cgp9HWBtTFKAVlWCAACyAbYHCQg938.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Cgp9HWBtTFKAVlWCAACyAbYHCQg938.png" alt="png" /></p>
<p>MySqlDialect 的继承关系图</p>
<p>在上图中PageHelper 是一个通用的 Dialect 实现,会将上述分页操作委托给当前线程绑定的 Dialect 实现进行处理,这主要是靠其中的 autoDialect 字段PageAutoDialect 类型实现的。AbstractDialect 中只提供了一个生成“查询总记录数”SQL 语句(即 select count(*) 语句)的功能。</p>
<p>AbstractRowBoundsDialect 这条继承线是针对 RowBounds 进行分页的 Dialect 实现,其中会根据 RowBounds 实现 Dialect 接口,例如,在 MySqlRowBoundsDialect 中的 getPageSql() 方法实现中会改写 SQL 语句,添加 limit 子句,其中的 offset、limit 参数均来自传入的 RowBounds 参数。</p>
@@ -286,7 +286,7 @@ return dialect.afterPage(resultList, parameter, rowBounds);
</ul>
<p>既然 MyBatis-Plus 在 MyBatis 之上提供了这么多的扩展,那么我们就来快速上手体验一下 MyBatis-Plus。这里我们依旧选用 MySQL 数据库,复用上面介绍 MyBatis-Generator 示例时用到的 test 库和 t_customer 表。</p>
<p>首先,新建一个 Spring Boot 项目,这里我们可以使用 Spring 官网提供的<a href="https://start.spring.io/?fileGuid=xxQTRXtVcqtHK6j8">项目生成器</a>快速生成,导入 IDEA 之后会发现 Spring Boot 的配置和启动类都已经生成好了,如下图所示:</p>
<p><img src="assets/Cgp9HWBtTGCAB50qAADaNi9sMew051.png" alt="Drawing 2.png" /></p>
<p><img src="assets/Cgp9HWBtTGCAB50qAADaNi9sMew051.png" alt="png" /></p>
<p>Spring Boot 示例项目的结构图</p>
<p>接下来我们打开 pom.xml 文件,看到其中已经自动添加了 Spring Boot 的全部依赖,此时只需要添加 mysql-connector-java 依赖以及 MyBatis-Plus 依赖即可(目前 MyBatis-Plus 最新版本是 3.4.2</p>
<pre><code>&lt;dependency&gt;