Mybatis框架源碼解讀
本內(nèi)容均屬原創(chuàng)內(nèi)容,轉(zhuǎn)載請(qǐng)注明出處:http://www.itdecent.cn/p/dc9fd739d829
有一起學(xué)習(xí)的小伙伴可以加老薛qq:1811112688 一起努力。
從今天開(kāi)始,我們需要詳細(xì)花一段時(shí)間系統(tǒng)的深入學(xué)習(xí)一下MyBatis框架底層的內(nèi)容,我們會(huì)從一下幾個(gè)方向展開(kāi)討論:
- MyBatis執(zhí)行過(guò)程
- MyBatis的Executor、StatementHandler、ParameterHandler、ResultSetHandler
- MyBatis的一級(jí)緩存,二級(jí)緩存
- MyBatis事務(wù)管理機(jī)制
- 鎖機(jī)制
(一):MyBatis執(zhí)行過(guò)程
1-1:編寫(xiě)測(cè)試用例
1-1-1: 環(huán)境要求
測(cè)試用例環(huán)境:
Maven:3.6 Idea:2018-3 jdk:11 Mybatis:3.4.6 MySql:8.0
MySql驅(qū)動(dòng)包:8.0.13
1-1-2:測(cè)試用例配置
1-1-2-1:pom文件配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mage</groupId>
<artifactId>helloMybatis01</artifactId>
<version>1.0-SNAPSHOT</version>
<name>helloMybatis01</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
</properties>
<dependencies>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql驅(qū)動(dòng)包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!-- junit測(cè)試包 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 日志文件管理包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
</dependencies>
<!-- 加載資源配置文件 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
1-1-2-2:mybatis.xml文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 環(huán)境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 數(shù)據(jù)庫(kù)連接相關(guān)配置 ,這里動(dòng)態(tài)獲取config.properties文件中的內(nèi)容-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/user" />
<property name="username" value="root" />
<property name="password" value="mage1234" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.mage.dao.UserDao"></mapper>
</mappers>
</configuration>
1-1-2-3:UserDao.java
public interface UserDao {
public User queryById(int id);
}
1-1-2-4:UserDao.xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mage.dao.UserDao" >
<sql id="QueryUserSql">
select * from t_user
</sql>
<select id="queryById" resultType="com.mage.vo.User" parameterType="int">
<include refid="QueryUserSql"></include>
where id = #{id}
</select>
</mapper>
1-1-2-5:測(cè)試類(lèi)
public class HiMyBatis{
private SqlSession session;
@Before
public void start() throws IOException {
//1:讀取配置信息 加載配置
InputStream is = Resources.getResourceAsStream("mybatis.xml");
//2:構(gòu)建SqlSessionFactory回話(huà)工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3:獲取SqlSession回話(huà)
this.session = sqlSessionFactory.openSession();
}
@After
public void destory(){
if(session!=null){
//關(guān)閉會(huì)話(huà)
session.close();
}
}
@Test
public void test() throws IOException {
//4:獲取代理對(duì)象
UserDao dao = session.getMapper(UserDao.class);
//5:獲取查詢(xún)結(jié)果
User user = dao.queryById(1);
System.out.println(user);
}
}
1-2:MyBatis層次結(jié)構(gòu)

1-2-1:SqlSession:
接收開(kāi)發(fā)人員提供Statement Id 和參數(shù).并返回操作結(jié)果:
主要負(fù)責(zé)【Connection獲取】和【Statement對(duì)象管理方案】
Statement對(duì)象管理方案
- 1)簡(jiǎn)單管理方案:一個(gè)Statement接口對(duì)象只執(zhí)行一次。執(zhí)行完畢 就會(huì)Statement接口對(duì)象進(jìn)行銷(xiāo)毀。
- 2)可重用方案: 使用一個(gè)Map集合,關(guān)鍵字就是一條Sql語(yǔ)句。對(duì)應(yīng)
內(nèi)容Statement接口對(duì)象,等到SqlSession再次接收到相同命令時(shí),就從map集合找到對(duì)應(yīng)Statement接口使用。
? map.put("select * from order", Statement1) - 3)批處理管理方案:將多個(gè)Statement包含的SQL語(yǔ)句,交給一個(gè)Statement對(duì)象 輸送到數(shù)據(jù)庫(kù),形成批處理操作
1-2-2: Executor:
MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負(fù)責(zé)SQL語(yǔ)句的生成和查詢(xún)緩存的維護(hù)。
Executor的繼承關(guān)系
- BaseExecutor:抽象類(lèi);減輕Executor接口實(shí)現(xiàn)難度。
- CachingExecutour:提高查詢(xún)效率,在查詢(xún)時(shí)首先到緩存中尋找對(duì)應(yīng)的數(shù)據(jù)。如果有直接返回,MyBatis框架默認(rèn)情況下使用執(zhí)行器緩存執(zhí)行器,
如果緩存執(zhí)行器沒(méi)有得到對(duì)應(yīng)結(jié)果時(shí),才會(huì)交給其他的執(zhí)行器執(zhí)行
1-2-3: StatementHandler:
封裝了JDBC Statement操作,負(fù)責(zé)對(duì)JDBC statement 的操作,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合。
1-2-4: ParameterHandler:
負(fù)責(zé)對(duì)用戶(hù)傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)
1-2-5: ResultSetHandler:
負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類(lèi)型的集合
1-2-6 : TypeHandler:
負(fù)責(zé)java數(shù)據(jù)類(lèi)型和jdbc數(shù)據(jù)類(lèi)型之間的映射和轉(zhuǎn)換
1-1-7: MappedStatement:
維護(hù)了一條<select|update|delete|insert>節(jié)點(diǎn)的封裝
1-1-8: SqlSource:
負(fù)責(zé)根據(jù)用戶(hù)傳遞的parameterObject,動(dòng)態(tài)地生成SQL語(yǔ)句,將信息封裝到BoundSql對(duì)象中,并返回BoundSql表示動(dòng)態(tài)生成的SQL語(yǔ)句以及相應(yīng)的參數(shù)信息
1-1-9: Configuration:
MyBatis所有的配置信息都維持在Configuration對(duì)象之中
1-3:MyBatis 架構(gòu)圖

PS:這個(gè)架構(gòu)圖,通過(guò)理解MyBatis的層級(jí)結(jié)構(gòu)嘗試?yán)斫饩涂梢粤恕F鋵?shí)和層級(jí)結(jié)構(gòu)圖闡述的思想是一致的。
1-4:SqlSessionFactory接口:
作用:主要負(fù)責(zé)【Connection獲取】和【Statement對(duì)象管理方案】
1-4-1:SqlSessionFactory的類(lèi)圖

1-4-2:SqlSessionManager分析
1-4-2-1:源碼
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
//請(qǐng)注意這里
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();
//第二步:內(nèi)部構(gòu)建SqlSeesion的代理對(duì)象
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
}
//第一步:假設(shè)調(diào)用當(dāng)前構(gòu)造器
public static SqlSessionManager newInstance(Reader reader) {
return new SqlSessionManager((new SqlSessionFactoryBuilder()).build(reader, (String)null, (Properties)null));
}
}
1-4-2-2:源碼解讀
這里老薛將大部分的構(gòu)造器刪除掉了,因?yàn)槠鋵?shí)核心的代碼已經(jīng)在上面了,就是調(diào)用private的構(gòu)造器創(chuàng)建對(duì)象
- [ ] 第一步:讀取流數(shù)據(jù),調(diào)用私有的構(gòu)造器,創(chuàng)建返回SqlSession對(duì)象
- [ ] 第二步:這里私有的構(gòu)造器內(nèi)部就是通過(guò)java提供的動(dòng)態(tài)代理創(chuàng)建了一個(gè)與之對(duì)應(yīng)的SqlSession的代理對(duì)象。
- [ ] 第三步:耐心的讀一下代理對(duì)象的創(chuàng)建方式,其實(shí)和我們之前聊過(guò)的動(dòng)態(tài)代理,和多級(jí)代理 一回顧其實(shí)很容易。
- [ ] 監(jiān)控
SqlSession.class這個(gè)接口中的所有方法,這里的方法都是核心業(yè)務(wù)方法,一旦被發(fā)現(xiàn)執(zhí)行,則交由SqlSessionManager中的內(nèi)部類(lèi)SqlSessionInterceptor對(duì)象去實(shí)現(xiàn)。
1-4-2-2-1:SqlSessionInterceptor類(lèi)的源碼:
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//注意這里的sqlSession的創(chuàng)建
SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
return method.invoke(sqlSession, args);
} catch (Throwable var12) {
throw ExceptionUtil.unwrapThrowable(var12);
}
} else {
//如果代理對(duì)象沒(méi)有將sqlSession對(duì)象創(chuàng)建則會(huì)重新創(chuàng)建一個(gè)
SqlSession autoSqlSession = SqlSessionManager.this.openSession();
Object var7;
try {
Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
var7 = result;
} catch (Throwable var13) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(var13);
} finally {
autoSqlSession.close();
}
return var7;
}
}
}
1-4-2-2-2:SqlSessionInterceptor源碼解讀:
1:這里明顯是一個(gè)代理實(shí)現(xiàn)類(lèi)對(duì)象
2:在內(nèi)部類(lèi)中的SqlSession對(duì)象其實(shí)是從外部類(lèi)中的ThreadLocal中獲取的。
3:如果ThreadLocal中不存在,根據(jù)跟蹤源碼,我們發(fā)現(xiàn)其實(shí)是通過(guò)創(chuàng)建的SqlSessionFactory重新帶開(kāi)了一個(gè)SqlSession對(duì)象,那么重點(diǎn)就在于創(chuàng)建的SqlSessionFactory是那個(gè)實(shí)現(xiàn)類(lèi)了。
public SqlSession openSession() {
return this.sqlSessionFactory.openSession();
}
1-4-2-2-3:為什么目前SqlSessionManager用的不多了?
原因就在與SqlSessionManager中的SqlSession為了保護(hù)線(xiàn)程安全,通過(guò)ThreadLocal做了線(xiàn)程安全,但是一般情況下,我們的MyBatis框架會(huì)和Spring框架一起使用,而此時(shí)SqlSession對(duì)象的線(xiàn)程安全就顯得不那么好用了。
1-4-3:DefaultSqlSessionFactory分析:
1-4-3-1:源碼
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
//1:會(huì)將配置文件讀取到Configuration對(duì)象中
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
//方法1
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
//方法2
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
DefaultSqlSession var8;
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException var13) {
autoCommit = true;
}
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
Transaction tx = transactionFactory.newTransaction(connection);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var14, var14);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
}
1-4-3-2:源碼解讀
這里老薛也將大部分的內(nèi)容都刪減了一下,留了一個(gè)對(duì)外可以訪問(wèn)的構(gòu)造器以及兩個(gè)核心方法。所有的構(gòu)造器的調(diào)用都是通過(guò)從數(shù)據(jù)源或者是鏈接中創(chuàng)建返回的。也就是兩個(gè)私有方法中獲取的。這里老薛解讀一個(gè):openSessionFromDataSource();方法
- 第一步:首先會(huì)將Configuration對(duì)象創(chuàng)建好,其實(shí)在MyBatis框架啟動(dòng)時(shí),就會(huì)讀取核心配置文件,即mybatis.xml文件,通過(guò)XMLConfigBuilder對(duì)象去將xml文件中的數(shù)據(jù)信息挨個(gè)進(jìn)行填充。
- 第二步:讀取Configuration中的Environment,其實(shí)就是我們配置的環(huán)境,可以獲取到配置的數(shù)據(jù)源、事物提交方式等信息。
- 第三步:獲取到TransactionFactory對(duì)象以及Executor執(zhí)行器對(duì)象。
- 第四步:通過(guò)配置對(duì)象、執(zhí)行器對(duì)象創(chuàng)建DefaultSqlSession
ps:最后留個(gè)小作業(yè):
1:查看一下SqlSession接口的實(shí)現(xiàn)類(lèi)和源碼,你是否發(fā)現(xiàn)相似之處呢?
2:通過(guò)run我們的第一個(gè)代碼,和對(duì)于層架結(jié)構(gòu)圖以及結(jié)構(gòu)圖,你是否可以通過(guò)DeBug方式,畫(huà)出來(lái)整個(gè)程序的流程圖呢?
參考內(nèi)容:http://www.mybatis.org/mybatis-3/zh/
以及MyBatis技術(shù)內(nèi)幕和深入淺出MyBatis技術(shù)原理書(shū)籍