Mybatis框架源碼解讀
本內(nèi)容均屬原創(chuàng)內(nèi)容枢步,轉(zhuǎn)載請注明出處:http://www.reibang.com/p/dc9fd739d829
有一起學(xué)習(xí)的小伙伴可以加老薛qq:1811112688 一起努力剔桨。
從今天開始,我們需要詳細(xì)花一段時間系統(tǒng)的深入學(xué)習(xí)一下MyBatis框架底層的內(nèi)容抖苦,我們會從一下幾個方向展開討論:
- MyBatis執(zhí)行過程
- MyBatis的Executor、StatementHandler米死、ParameterHandler锌历、ResultSetHandler
- MyBatis的一級緩存,二級緩存
- MyBatis事務(wù)管理機(jī)制
- 鎖機(jī)制
(一):MyBatis執(zhí)行過程
1-1:編寫測試用例
1-1-1: 環(huán)境要求
測試用例環(huán)境:
Maven:3.6 Idea:2018-3 jdk:11 Mybatis:3.4.6 MySql:8.0
MySql驅(qū)動包:8.0.13
1-1-2:測試用例配置
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ū)動包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!-- junit測試包 -->
<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ù)庫連接相關(guān)配置 ,這里動態(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:測試類
public class HiMyBatis{
private SqlSession session;
@Before
public void start() throws IOException {
//1:讀取配置信息 加載配置
InputStream is = Resources.getResourceAsStream("mybatis.xml");
//2:構(gòu)建SqlSessionFactory回話工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3:獲取SqlSession回話
this.session = sqlSessionFactory.openSession();
}
@After
public void destory(){
if(session!=null){
//關(guān)閉會話
session.close();
}
}
@Test
public void test() throws IOException {
//4:獲取代理對象
UserDao dao = session.getMapper(UserDao.class);
//5:獲取查詢結(jié)果
User user = dao.queryById(1);
System.out.println(user);
}
}
1-2:MyBatis層次結(jié)構(gòu)
1-2-1:SqlSession:
接收開發(fā)人員提供Statement Id 和參數(shù).并返回操作結(jié)果:
主要負(fù)責(zé)【Connection獲取】和【Statement對象管理方案】
Statement對象管理方案
- 1)簡單管理方案:一個Statement接口對象只執(zhí)行一次峦筒。執(zhí)行完畢 就會Statement接口對象進(jìn)行銷毀究西。
- 2)可重用方案: 使用一個Map集合,關(guān)鍵字就是一條Sql語句物喷。對應(yīng)
內(nèi)容Statement接口對象,等到SqlSession再次接收到相同命令時卤材,就從map集合找到對應(yīng)Statement接口使用遮斥。
? map.put("select * from order", Statement1) - 3)批處理管理方案:將多個Statement包含的SQL語句,交給一個Statement對象 輸送到數(shù)據(jù)庫商膊,形成批處理操作
1-2-2: Executor:
MyBatis執(zhí)行器伏伐,是MyBatis 調(diào)度的核心,負(fù)責(zé)SQL語句的生成和查詢緩存的維護(hù)晕拆。
Executor的繼承關(guān)系
- BaseExecutor:抽象類藐翎;減輕Executor接口實(shí)現(xiàn)難度。
- CachingExecutour:提高查詢效率实幕,在查詢時首先到緩存中尋找對應(yīng)的數(shù)據(jù)吝镣。如果有直接返回,MyBatis框架默認(rèn)情況下使用執(zhí)行器緩存執(zhí)行器昆庇,
如果緩存執(zhí)行器沒有得到對應(yīng)結(jié)果時末贾,才會交給其他的執(zhí)行器執(zhí)行
1-2-3: StatementHandler:
封裝了JDBC Statement操作,負(fù)責(zé)對JDBC statement 的操作整吆,如設(shè)置參數(shù)拱撵、將Statement結(jié)果集轉(zhuǎn)換成List集合。
1-2-4: ParameterHandler:
負(fù)責(zé)對用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)
1-2-5: ResultSetHandler:
負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對象轉(zhuǎn)換成List類型的集合
1-2-6 : TypeHandler:
負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換
1-1-7: MappedStatement:
維護(hù)了一條<select|update|delete|insert>節(jié)點(diǎn)的封裝
1-1-8: SqlSource:
負(fù)責(zé)根據(jù)用戶傳遞的parameterObject表蝙,動態(tài)地生成SQL語句拴测,將信息封裝到BoundSql對象中,并返回BoundSql表示動態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息
1-1-9: Configuration:
MyBatis所有的配置信息都維持在Configuration對象之中
1-3:MyBatis 架構(gòu)圖
PS:這個架構(gòu)圖府蛇,通過理解MyBatis的層級結(jié)構(gòu)嘗試?yán)斫饩涂梢粤思鳌F鋵?shí)和層級結(jié)構(gòu)圖闡述的思想是一致的。
1-4:SqlSessionFactory接口:
作用:主要負(fù)責(zé)【Connection獲取】和【Statement對象管理方案】
1-4-1:SqlSessionFactory的類圖
1-4-2:SqlSessionManager分析
1-4-2-1:源碼
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
//請注意這里
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();
//第二步:內(nèi)部構(gòu)建SqlSeesion的代理對象
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)建對象
- [ ] 第一步:讀取流數(shù)據(jù),調(diào)用私有的構(gòu)造器穷遂,創(chuàng)建返回SqlSession對象
- [ ] 第二步:這里私有的構(gòu)造器內(nèi)部就是通過java提供的動態(tài)代理創(chuàng)建了一個與之對應(yīng)的SqlSession的代理對象函匕。
- [ ] 第三步:耐心的讀一下代理對象的創(chuàng)建方式,其實(shí)和我們之前聊過的動態(tài)代理蚪黑,和多級代理 一回顧其實(shí)很容易浦箱。
- [ ] 監(jiān)控
SqlSession.class
這個接口中的所有方法,這里的方法都是核心業(yè)務(wù)方法祠锣,一旦被發(fā)現(xiàn)執(zhí)行酷窥,則交由SqlSessionManager中的內(nèi)部類SqlSessionInterceptor對象去實(shí)現(xiàn)。
1-4-2-2-1:SqlSessionInterceptor類的源碼:
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 {
//如果代理對象沒有將sqlSession對象創(chuàng)建則會重新創(chuàng)建一個
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:這里明顯是一個代理實(shí)現(xiàn)類對象
2:在內(nèi)部類中的SqlSession對象其實(shí)是從外部類中的ThreadLocal中獲取的伴网。
3:如果ThreadLocal中不存在蓬推,根據(jù)跟蹤源碼,我們發(fā)現(xiàn)其實(shí)是通過創(chuàng)建的SqlSessionFactory重新帶開了一個SqlSession對象澡腾,那么重點(diǎn)就在于創(chuàng)建的SqlSessionFactory是那個實(shí)現(xiàn)類了沸伏。
public SqlSession openSession() {
return this.sqlSessionFactory.openSession();
}
1-4-2-2-3:為什么目前SqlSessionManager用的不多了?
原因就在與SqlSessionManager中的SqlSession為了保護(hù)線程安全糕珊,通過ThreadLocal做了線程安全,但是一般情況下毅糟,我們的MyBatis框架會和Spring框架一起使用红选,而此時SqlSession對象的線程安全就顯得不那么好用了。
1-4-3:DefaultSqlSessionFactory分析:
1-4-3-1:源碼
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
//1:會將配置文件讀取到Configuration對象中
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òu)造器以及兩個核心方法喇肋。所有的構(gòu)造器的調(diào)用都是通過從數(shù)據(jù)源或者是鏈接中創(chuàng)建返回的。也就是兩個私有方法中獲取的迹辐。這里老薛解讀一個:openSessionFromDataSource();方法
- 第一步:首先會將Configuration對象創(chuàng)建好蝶防,其實(shí)在MyBatis框架啟動時,就會讀取核心配置文件明吩,即mybatis.xml文件间学,通過XMLConfigBuilder對象去將xml文件中的數(shù)據(jù)信息挨個進(jìn)行填充。
- 第二步:讀取Configuration中的Environment印荔,其實(shí)就是我們配置的環(huán)境低葫,可以獲取到配置的數(shù)據(jù)源、事物提交方式等信息仍律。
- 第三步:獲取到TransactionFactory對象以及Executor執(zhí)行器對象嘿悬。
- 第四步:通過配置對象、執(zhí)行器對象創(chuàng)建DefaultSqlSession
ps:最后留個小作業(yè):
1:查看一下SqlSession接口的實(shí)現(xiàn)類和源碼染苛,你是否發(fā)現(xiàn)相似之處呢鹊漠?
2:通過run我們的第一個代碼主到,和對于層架結(jié)構(gòu)圖以及結(jié)構(gòu)圖茶行,你是否可以通過DeBug方式,畫出來整個程序的流程圖呢?
參考內(nèi)容:http://www.mybatis.org/mybatis-3/zh/
以及MyBatis技術(shù)內(nèi)幕和深入淺出MyBatis技術(shù)原理書籍