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

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)

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

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)圖

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容