一周學完MyBatis源碼,萬字總結(jié)
點擊下方“IT牧場”,選擇“設為星標”
之前,我給大家分享給很多MyBatis源碼分析的一系列文章。今天,就自己的感受來做一個整體的總結(jié)。?
眾所周知,MyBatis是對JDBC進行封裝而成的產(chǎn)品,所以,聊MyBatis源碼之前我們得先了解JDBC。
JDBC
JDBC案例:
public?class?JdbcDemo?{
????public?static?final?String?URL?=?"jdbc:mysql://localhost:3306/mblog";
????public?static?final?String?USER?=?"root";
????public?static?final?String?PASSWORD?=?"123456";
????public?static?void?main(String[]?args)?throws?Exception?{?
????????Class.forName("com.mysql.jdbc.Driver");?
????????Connection?conn?=?DriverManager.getConnection(URL,?USER,?PASSWORD);?
????????Statement?stmt?=?conn.createStatement();?
????????ResultSet?rs?=?stmt.executeQuery("SELECT?id,?name,?age?FROM?m_user?where?id?=1");?
????????while(rs.next()){
????????????System.out.println("name:?"+rs.getString("name")+"?年齡:"+rs.getInt("age"));
????????}
????}
}
說明:
數(shù)據(jù)庫驅(qū)動:
Class.forName("com.mysql.jdbc.Driver");?
獲取連接:
Connection?conn?=?DriverManager.getConnection(URL,?USER,?PASSWORD);?
創(chuàng)建Statement或者PreparedStatement對象:
Statement?stmt?=?conn.createStatement();?
執(zhí)行sql數(shù)據(jù)庫查詢:
ResultSet?rs?=?stmt.executeQuery("SELECT?id,?name,?age?FROM?m_user?where?id?=1");?
解析結(jié)果集:
System.out.println("name:?"+rs.getString("name")+"?年齡:"+rs.getInt("age"));
在使用的時候,業(yè)務處理完成后記得關(guān)閉相關(guān)資源
使用過JDCB的朋友都知道,JDBC如果用到我們項目中基本上都會存在以下幾個問題:
傳統(tǒng)JDBC的問題
創(chuàng)建數(shù)據(jù)庫的連接存在大量的硬編碼, 執(zhí)行statement時存在硬編碼. 頻繁的開啟和關(guān)閉數(shù)據(jù)庫連接,會嚴重影響數(shù)據(jù)庫的性能,浪費數(shù)據(jù)庫的資源. 存在大量的重復性編碼
針對上面這些問題,于是一大堆持久化框架應運而生。
持久化框
做持久層的框架有很多,有orm系和utils系列。
orm系列的代表有:hibernate,eclipseLink,topLink;utils系列的代表有:MyBatis,dbUtils,jdbcTemplate等;
下面對于這些框架做個簡單概述:
至于 jpa,它只是一個規(guī)范標準,并非具體框架,不等同于 spring-data-jpa;同時 spring-data-jpa 也不是 jpa 的具體實現(xiàn),它只是 jpa 規(guī)范標準的進一步封裝。hibernate 是 jpa 最為常見的實現(xiàn)框架,當然其他還有 eclipseLink,topLink。
MyBatis 的特點是在對 SQL 優(yōu)化時,復雜 SQL 的優(yōu)化可控性高,框架內(nèi)部調(diào)用層次簡單,除了部分可以自動生成代碼,還會有很多 SQL 需要自行編碼。spring-data-jpa(hibernate) 的特點是在開發(fā)過程中,脫離 SQL 編碼開發(fā),當然也支持本地SQL來查詢,框架內(nèi)部調(diào)用層次復雜。以上就可以根據(jù)實際的業(yè)務進度和業(yè)務支撐情況做出選擇了。
其實可以在一個項目在同時支持 MyBatis 和 spring-data-jpa,復雜SQL走 mybatis,常用SQL走 spring-data-jpa。
鑒于前實際開發(fā)中使用數(shù)量,我們選擇MyBatis 進行分析。
mybatis
新加入開發(fā)的朋友,估計不知道MyBatis 的前身,在2010年之前,不交MyBatis ,叫ibatis。
MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數(shù)以及獲取結(jié)果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄 。
特點
簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar文件+配置幾個sql映射文件易于學習,易于使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實現(xiàn)。 靈活:MyBatis 不會對應用程序或者數(shù)據(jù)庫的現(xiàn)有設計強加任何影響。sql寫在xml里,便于統(tǒng)一管理和優(yōu)化。通過sql語句可以滿足操作數(shù)據(jù)庫的所有需求。 解除sql與程序代碼的耦合:通過提供DAO層,將業(yè)務邏輯和數(shù)據(jù)訪問邏輯分離,使系統(tǒng)的設計更清晰,更易維護,更易單元測試。sql和代碼的分離,提高了可維護性。 提供映射標簽,支持對象與數(shù)據(jù)庫的orm字段關(guān)系映射 提供對象關(guān)系映射標簽,支持對象關(guān)系組建維護 提供xml標簽,支持編寫動態(tài)sql。
案例
需要來源兩個jar包:MyBatis的jar包和MySQL數(shù)據(jù)庫連接jar包。
<dependency>
??<groupId>org.mybatisgroupId>
??<artifactId>mybatisartifactId>
??<version>x.x.xversion>
dependency>
<dependency>
???<groupId>mysqlgroupId>
???<artifactId>mysql-connector-javaartifactId>
???<version>8.0.16version>
dependency>
創(chuàng)建一個表t_user(數(shù)據(jù)庫也是肯定要自己創(chuàng)建的哈)
?CREATE?TABLE?`t_user`?(
??????`id`?int(11)?NOT?NULL?AUTO_INCREMENT,
??????`name`?varchar(255)?DEFAULT?NULL,
??????`age`?int(11)?DEFAULT?NULL,
??????PRIMARY?KEY?(`id`)
)?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4;
插入一條數(shù)據(jù):
INSERT?INTO?`t_user`?VALUES?('1',?'tian',?'19',?'1');
創(chuàng)建該數(shù)據(jù)庫表的實體類:
public?class?User?{
????private?Integer?id;
????private?String?name;
????private?Integer?age;
????//set?get
}
創(chuàng)建mapper配置文件:UserMapper.xml
mapper
????????PUBLIC?"-//mybatis.org//DTD?Mapper?3.0//EN"
????????"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper?namespace="com.tian.mapper.UserMapper">
????<select?id="selectUserById"?resultType="com.tian.domain.User">
????????select?*?from?t_user?where?id?=?#{id}
????select>
mapper>
創(chuàng)建mapper接口:UserMapper.java
import?com.tian.domain.User;
public?interface?UserMapper?{
????User?selectUserById(Integer?id);
}
MyBatis 整體配置文件:
configuration
????????PUBLIC?"-//mybatis.org//DTD?Config?3.0//EN"
????????"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
????<environments?default="development">
????????<environment?id="development">
????????????<transactionManager?type="JDBC"/>
????????????<dataSource?type="POOLED">
????????????????<property?name="driver"?value="com.mysql.cj.jdbc.Driver"/>
????????????????<property?name="url"?value="jdbc:mysql://localhost:3306/mblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=UTC"/>
????????????????<property?name="username"?value="root"/>
????????????????<property?name="password"?value="123456"/>
????????????dataSource>
????????environment>
????environments>
????<mappers>
????????<mapper?resource="mappers/UserMapper.xml"/>
????mappers>
configuration>
上面這些就是我們使用MyBatis基本開發(fā)代碼。
下面我們來寫一個測試類:
import?com.tian.domain.User;
import?com.tian.mapper.UserMapper;
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?MybatisApplication?{
????public?static?void?main(String[]?args)?{
????????String?resource?=?"mybatis-config.xml";
????????InputStream?inputStream?=?null;
????????SqlSession?sqlSession?=null;
????????try?{
????????????//讀取配置文件
????????????inputStream?=?Resources.getResourceAsStream(resource);
????????????//創(chuàng)建SqlSession工廠
????????????SqlSessionFactory?sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(inputStream);
????????????//創(chuàng)建sql操作會話
????????????sqlSession?=?sqlSessionFactory.openSession();
????????????UserMapper?userMapper=sqlSession.getMapper(UserMapper.class);
????????????//獲取數(shù)據(jù)并解析成User對象
????????????User?user?=?userMapper.selectUserById(1);
????????????//輸出
????????????System.out.println(user);
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}finally?{
????????????//關(guān)閉相關(guān)資源
????????????try?{
????????????????inputStream.close();
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????????sqlSession.close();
????????}
????}
}
測試結(jié)果:
User{id=1, name='tian', age=19}
如上面的代碼所示,SqlSession是MyBatis中提供的與數(shù)據(jù)庫交互的接口,SqlSession實例通過工廠模式創(chuàng)建。
為了創(chuàng)建SqlSession對象,首先需要創(chuàng)建SqlSessionFactory對象,而SqlSessionFactory對象的創(chuàng)建依賴于SqlSessionFactoryBuilder類,該類提供了一系列重載的build()方法,我們需要以主配置文件的輸入流作為參數(shù)調(diào)用SqlSessionFactoryBuilder對象的bulid()方法,該方法返回一個SqlSessionFactory對象。
有了SqlSessionFactory對象之后,調(diào)用SqlSessionFactory對象的openSession()方法即可獲取一個與數(shù)據(jù)庫建立連接的SqlSession實例。
前面我們定義了UserMapper接口,這里需要調(diào)用SqlSession的getMapper()方法創(chuàng)建一個動態(tài)代理對象,然后調(diào)用UserMapper代理實例的方法即可完成與數(shù)據(jù)庫的交互。
針對上面這個案例,我們來梳理一下MyBatis的整體執(zhí)行流程和核心組件。
MyBatis 核心組件
MyBatis 執(zhí)行流程

Configuration
用于描述MyBatis的主配置信息,其他組件需要獲取配置信息時,直接通過Configuration對象獲取。除此之外,MyBatis在應用啟動時,將Mapper配置信息、類型別名、TypeHandler等注冊到Configuration組件中,其他組件需要這些信息時,也可以從Configuration對象中獲取。
MappedStatement
MappedStatement用于描述Mapper中的SQL配置信息,是對Mapper XML配置文件中等標簽或者@Select/@Update等注解配置信息的封裝。
SqlSession
SqlSession是MyBatis提供的面向用戶的API,表示和數(shù)據(jù)庫交互時的會話對象,用于完成數(shù)據(jù)庫的增刪改查功能。SqlSession是Executor組件的外觀,目的是對外提供易于理解和使用的數(shù)據(jù)庫操作接口。
Executor
Executor是MyBatis的SQL執(zhí)行器,MyBatis中對數(shù)據(jù)庫所有的增刪改查操作都是由Executor組件完成的。
StatementHandler
StatementHandler封裝了對JDBC Statement對象的操作,比如為Statement對象設置參數(shù),調(diào)用Statement接口提供的方法與數(shù)據(jù)庫交互,等等。
ParameterHandler
當MyBatis框架使用的Statement類型為CallableStatement和PreparedStatement時,ParameterHandler用于為Statement對象參數(shù)占位符設置值。
ResultSetHandler
ResultSetHandler封裝了對JDBC中的ResultSet對象操作,當執(zhí)行SQL類型為SELECT語句時,ResultSetHandler用于將查詢結(jié)果轉(zhuǎn)換成Java對象。
TypeHandler
TypeHandler是MyBatis中的類型處理器,用于處理Java類型與JDBC類型之間的映射。它的作用主要體現(xiàn)在能夠根據(jù)Java類型調(diào)用PreparedStatement或CallableStatement對象對應的setXXX()方法為Statement對象設置值,而且能夠根據(jù)Java類型調(diào)用ResultSet對象對應的getXXX()獲取SQL執(zhí)行結(jié)果。
使用JDBC API開發(fā)應用程序,其中一個比較煩瑣的環(huán)節(jié)是處理JDBC類型與Java類型之間的轉(zhuǎn)換。涉及Java類型和JDBC類型轉(zhuǎn)換的兩種情況如下:
PreparedStatement對象為參數(shù)占位符設置值時,需要調(diào)用PreparedStatement接口中提供的一系列的setXXX()方法,將Java類型轉(zhuǎn)換為對應的JDBC類型并為參數(shù)占位符賦值。執(zhí)行SQL語句獲取 ResultSet對象后,需要調(diào)用ResultSet對象的getXXX()方法獲取字段值,此時會將JDBC類型轉(zhuǎn)換為Java類型。
MyBatis提供的TypeHandler及與Java類型和JDBC類型之間的對應關(guān)系:

小結(jié)
我們使用到了SqlSession組件,它是用戶層面的API。實際上SqlSession是Executor組件的外觀,目的是為用戶提供更友好的數(shù)據(jù)庫操作接口,這是設計模式中外觀模式的典型應用。
真正執(zhí)行SQL操作的是Executor組件,Executor可以理解為SQL執(zhí)行器,它會使用StatementHandler組件對JDBC的Statement對象進行操作。
當Statement類型為CallableStatement和PreparedStatement時,會通過ParameterHandler組件為參數(shù)占位符賦值。ParameterHandler組件中會根據(jù)Java類型找到對應的TypeHandler對象,TypeHandler中會通過Statement對象提供的setXXX()方法(例如setString()方法)為Statement對象中的參數(shù)占位符設置值。
StatementHandler組件使用JDBC中的Statement對象與數(shù)據(jù)庫完成交互后,當SQL語句類型為SELECT時,MyBatis通過ResultSetHandler組件從Statement對象中獲取ResultSet對象,然后將ResultSet對象轉(zhuǎn)換為Java對象。
高級技能
設計模式
在MyBatis 中大量的使用了設計模式,在MyBatis 我們可以學到一下幾種設計模式:
工廠模式 模板方法模式 代理模式 建造者模式 單例模式 適配模式 裝飾器模式 責任鏈模式 ....
緩存
在 Web 應用中,緩存是必不可少的組件。通常我們都會用 Redis 或 memcached 等緩存中間件,攔截大量奔向數(shù)據(jù)庫的請求,減輕數(shù)據(jù)庫壓力。作為一個重要的組件,MyBatis 自然也在內(nèi)部提供了相應的支持。通過在框架層面增加緩存功能,可減輕數(shù)據(jù)庫的壓力,同時又可以提升查詢速度,可謂一舉兩得。
MyBatis 緩存結(jié)構(gòu)由一級緩存和二級緩存構(gòu)成,這兩級緩存均是使用 Cache 接口的實現(xiàn)類。因此,在接下里的章節(jié)中,我將首先會向大家介紹 Cache 幾種實現(xiàn)類的源碼,然后再分析一級和二級緩存的實現(xiàn)。
MyBatis一級緩存和二級緩存的使用:MyBatis一級緩存是SqlSession級別的緩存,默認就是開啟的,而且無法關(guān)閉;二級緩存需要在MyBatis主配置文件中通過設置cacheEnabled參數(shù)值來開啟。
一級緩存是在Executor中實現(xiàn)的。MyBatis的Executor組件有3種不同的實現(xiàn),分別為SimpleExecutor、ReuseExecutor和BatchExecutor。這些類都繼承自BaseExecutor,在BaseExecutor類的query()方法中,首先從緩存中獲取查詢結(jié)果,如果獲取不到,則從數(shù)據(jù)庫中查詢結(jié)果,然后將查詢結(jié)果緩存起來。而MyBatis的二級緩存則是通過裝飾器模式實現(xiàn)的,當通過cacheEnabled參數(shù)開啟了二級緩存,MyBatis框架會使用CachingExecutor對SimpleExecutor、ReuseExecutor或者BatchExecutor進行裝飾,當執(zhí)行查詢操作時,對查詢結(jié)果進行緩存,執(zhí)行更新操作時則更新二級緩存。本章最后介紹了MyBatis如何整合Redis作為二級緩存。
除此之外,MyBatis還支持Ehcache、OSCache等,這種特性并不常用。
插件
大多數(shù)框架,都支持插件,用戶可通過編寫插件來自行擴展功能,Mybatis也不例外。
MyBatis提供了擴展機制,能夠在執(zhí)行Mapper時改變SQL的執(zhí)行行為。這種擴展機制是通過攔截器來實現(xiàn)的,用戶自定義的攔截器也被稱為MyBatis插件。
MyBatis框架支持對Executor、ParameterHandler、ResultSetHandler、StatementHandler四種組件的方法進行攔截。掌握了MyBatis插件的實現(xiàn)原理,然后自己實現(xiàn)一個分頁查詢插件和慢SQL統(tǒng)計插件,當我們需要的功能MyBatis框架無法滿足時,可以考慮通過自定義插件來實現(xiàn)。
經(jīng)典實現(xiàn):PageHelper 。
日志
MyBatis針對不同的日志框架提供對Log接口對應的實現(xiàn),Log接口的實現(xiàn)類下圖所示。從實現(xiàn)類可以看出,MyBatis支持7種不同的日志實現(xiàn),具體如下。

下面對常用幾個做一個簡單說明:
Apache Commons Logging:使用JCL輸出日志。 Log4j 2:使用Log4j 2框架輸入日志。 Java Util Logging:使用JDK內(nèi)置的日志模塊輸出日志。 Log4j:使用Log4j框架輸出日志。 No Logging:不輸出任何日志。 SLF4J:使用SLF4J日志門面輸出日志。 Stdout:將日志輸出到標準輸出設備(例如控制臺)。
MyBatis查找日志框架的順序為
SLF4J→JCL→Log4j2→Log4j→JUL→No Logging。
如果Classpath下不存在任何日志框架,則使用NoLoggingImpl日志實現(xiàn)類,即不輸出任何日志。
動態(tài)SQL綁定
動態(tài)SQL指的是事先無法預知具體的條件,需要在運行時根據(jù)具體的情況動態(tài)地生成SQL語句。
在MyBatis中有著豐富的動態(tài)SQL標簽,比如:、、、等。
在MyBatis源碼中有個SqlSource對象會作為MappedStatement對象的屬性保存在MappedStatement對象中。執(zhí)行Mapper時,會根據(jù)傳入的參數(shù)信息調(diào)用SqlSource對象的getBoundSql()方法獲取BoundSql對象,這個過程就完成了將SqlNode對象轉(zhuǎn)換為SQL語句的過程。
比如:,#{}占位符會被替換為“?”,然后調(diào)用JDBC中PreparedStatement對象的setXXX()方法為參數(shù)占位符設置值,而${}占位符則會直接替換為傳入的參數(shù)文本內(nèi)容。
推薦:掌握Mybatis動態(tài)映射,我可是下了功夫的
總結(jié)
本文是對MyBatis源碼分析的整體感受,希望對你有點點幫助。
咱們不是為了看源碼而看源碼,更不是為了裝B而看源碼,更多是我們從這個源碼中學到了什么。比如學到了設計模式是怎么使用的,學會了插件的思想,學會了緩存是怎么使用的等等。
個人覺得學習MyBatis源碼有幾個好處:
掌握Mybatis整體思想 學到一些核心技能 對于實際工作中排查問題有所幫助 面試還可以吹吹牛
干貨分享
最近將個人學習筆記整理成冊,使用PDF分享。關(guān)注我,回復如下代碼,即可獲得百度盤地址,無套路領取!
?001:《Java并發(fā)與高并發(fā)解決方案》學習筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學習筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領域驅(qū)動設計速成)》?007:全部?008:加技術(shù)群討論
加個關(guān)注不迷路
喜歡就點個"在看"唄^_^
