碼歌老薛MyBatis源碼解讀,認(rèn)識MyBatis的層級結(jié)構(gòu)和SqlSessionFactory(一)

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)

層級結(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)圖

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的類圖

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();方法

  1. 第一步:首先會將Configuration對象創(chuàng)建好蝶防,其實(shí)在MyBatis框架啟動時,就會讀取核心配置文件明吩,即mybatis.xml文件间学,通過XMLConfigBuilder對象去將xml文件中的數(shù)據(jù)信息挨個進(jìn)行填充。
  2. 第二步:讀取Configuration中的Environment印荔,其實(shí)就是我們配置的環(huán)境低葫,可以獲取到配置的數(shù)據(jù)源、事物提交方式等信息仍律。
  3. 第三步:獲取到TransactionFactory對象以及Executor執(zhí)行器對象嘿悬。
  4. 第四步:通過配置對象、執(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ù)原理書籍

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末登钥,一起剝皮案震驚了整個濱河市畔师,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牧牢,老刑警劉巖看锉,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異塔鳍,居然都是意外死亡伯铣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門轮纫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腔寡,“玉大人,你說我怎么就攤上這事掌唾》徘埃” “怎么了忿磅?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凭语。 經(jīng)常有香客問我葱她,道長,這世上最難降的妖魔是什么似扔? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任吨些,我火速辦了婚禮,結(jié)果婚禮上虫几,老公的妹妹穿的比我還像新娘锤灿。我一直安慰自己,他們只是感情好辆脸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布但校。 她就那樣靜靜地躺著,像睡著了一般啡氢。 火紅的嫁衣襯著肌膚如雪状囱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天倘是,我揣著相機(jī)與錄音亭枷,去河邊找鬼。 笑死搀崭,一個胖子當(dāng)著我的面吹牛叨粘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘤睹,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼升敲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了轰传?” 一聲冷哼從身側(cè)響起驴党,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎获茬,沒想到半個月后港庄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恕曲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年鹏氧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佩谣。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡把还,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笨篷,我是刑警寧澤瞳秽,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站率翅,受9級特大地震影響练俐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冕臭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一腺晾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辜贵,春花似錦悯蝉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厚棵,卻和暖如春蕉世,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背婆硬。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工狠轻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彬犯。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓向楼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谐区。 傳聞我的和親對象是個殘疾皇子湖蜕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348