【筆記】拉勾Java工程師高薪訓練營-第一階段 開源框架源碼解析-模組一 持久層框架涉及實現及MyBatis源碼分析-任務一:自定義持久層框架

以下筆記是我看完影片之後總結整理的,部分較為基礎的知識點也做了補充,如有問題歡迎溝通。

任務一:自定義持久層框架

1.1 JDBC回顧及問題分析

JDBC問題分析:

問題 解決方案
1 資料庫配置資訊存在硬編碼 使用配置文件
2 頻繁創建釋放資料庫連接 使用連接池
3 SQL語句、設置參數、獲取結果參數存在硬編碼問題 使用配置文件
4 手動封裝返回結果集,較為繁瑣 使用反射、內省

注意:雖然有兩個配置文件,但不建議放到一個文件中,因為一個是經常修改的,一個是幾乎不變的,而且邏輯上也不合理,只不過可以通過特殊方式不用手動載入每一個xml文件,具體見1.3.2 sqlMapConfig.xml詳解

1.2 自定義持久層框架思路分析

使用端:(項目)引入自定義持久層框架的jar包
提供兩部分配置資訊:資料庫配置資訊、SQL配置資訊(SQL語句、參數類型、返回值類型)

  1. sqlMapConfig.xml:存放資料庫配置資訊
  2. mapper.xml:存放sql配置資訊

自定義框架層本身:(工程)本質就是對JDBC程式碼進行封裝

  1. 載入配置文件:根據配置文件路徑,載入配置文件成位元組輸入流,存儲在記憶體中
    創建Resource類,方法:InputStream getResourceAsStream(String path)
  2. 創建兩個JavaBean(容器對象),存放的就是配置文件解析出來的內容
    Configuration:核心配置類,存放sqlMapConfig.xml解析出來的內容
    MapperStatement:映射配置類,存放mapper.xml解析出來的內容
  3. 解析配置文件:dom4j
    創建類:SqlSessionFactoryBuilder包含方法build(InputStream in)主要做兩件事:
    3.1 使用dom4j解析配置文件,將解析出來的內容封裝到容器對象中
    3.2 創建sqlSessionFactory對象,生產SqlSession,即會話對象(增刪改查都封裝在這裡),這裡用到了工廠模式,降低程式間的耦合(不直接new一個SqlSession)
  4. 創建SqlSessionFactory介面及實現類DefaultSqlSessionFactory,方法openSession()用以生產sqlSession
  5. 創建SqlSession介面及實現類DefaultSession,定義對資料庫的CRUD操作(selectList、selectOne、update……)
  6. 創建Executor介面及實現類SimpleExecutor實現類,query(Configuration,MapperStatement,MapperStatement,Object… params)方法,執行的就是JDBC程式碼

1.3 IPersistence_Test編寫

IPersistence_Test可以認為是使用端,首先完成使用端編寫,相對比較簡單,要做的就是編寫配置文件以及引入jar包

兩類配置文件:

sqlMapConfig.xml : 寫資料庫連接相關的配置資訊
XXXMapper.xml : (比如UserMapper.xml) 寫特定模組(比如用戶模組)的語句資訊

1.3.1 XXXMapper.xml詳解

XXXMapper.xml中主要完成以下功能:

  1. 配置真正需要在MySql裡面執行的sql語句
  2. 實現特定語句的定位,也就是需要給sql一個特定標識,即statementId
  3. 指定返回結果的封裝類型
  4. 指定傳入參數的類型

但是這麼寫只能操作user表,假設有了product表,那麼都使用selectList就無法確定使用的究竟是哪一個語句,所以要添加命名空間namespace

<mapper namespace="user">
    <!--sql的唯一標識:namespace.id來組成,statementId-->

    <!--為了能定位到特定的select語句,就要給一個id-->
    <!--resultType代表了想要封裝的實體,所以就要通過反射從相應路徑下獲取對應實體及屬性-->
    <select id="selectList" resultType="com.jlgl.pojo.User">
        select * from user
    </select>

    <!--這條sql語句執行的時候是需要參數的-->
    <!--多個參數傳遞,要有面向對象的思想,把多個對象封裝到某個實體-->
    <!--
        User user=new User();
        user.setId();
        user.setUsername();
    -->
    <!--所以參數也需要通過反射獲取對應實體及屬性-->
    <!--為了把對應的屬性賦值到對應的佔位符上,就要把?改成自定義佔位符-->
    <select id="selectOne" resultType="com.jlgl.pojo.User" paramterType="com.jlgl.pojo.User">
        select * from user where id =#{id} and username = #{username}
    </select>

</mapper>

1.3.2 sqlMapConfig.xml詳解

為了Resource載入xml文件時只載入一次,就需要把mapper.xml也存放進來,需要mapper.xml的全路徑

<configuration>

    <!--資料庫配置資訊-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://120.48.8.213:3306/audio_2020"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root1028*1"></property>
    </dataSource>

    <!--為了Resource載入xml文件時只載入一次,就需要把mapper.xml也存放進來,需要mapper.xml的全路徑-->
    <mapper resource="UserMapper.xml"></mapper>

</configuration>

1.4 Resources類定義

自定義持久層框架本質上就是利用dom4j技術,對使用端寫好的xml文件進行分析,然後封裝到配置類中,最終配置類被層層傳遞到Executor的query方法中執行JDBC程式碼

Resources類裡面定義一個方法,目的是將配置文件載入成位元組輸入流,存儲在記憶體中

1.5 容器對象定義

容器對象:MapperStatement和Configuration,這兩個容器對象存放的就是配置文件解析出來的內容

MapperStatement作用是從xxxMapper裡面讀取內容,所以xxxMapper的各個屬性都需要被存儲在MapperStatement對應的類屬性

Configuration作用是存放的就是sqlMapConfig的內容,其實就是資料庫配置資訊

1.6 解析核心配置文件sqlMapConfig

如前所述,解析核心配置文件需要兩步:

3. 解析配置文件:dom4j
創建類:SqlSessionFactoryBuilder包含方法build(InputStream in)主要做兩件事:
3.1 使用dom4j解析配置文件,將解析出來的內容封裝到容器對象中
3.2 創建sqlSessionFactory對象,生產SqlSession,即會話對象(增刪改查都封裝在這裡),這裡用到了工廠模式,降低程式間的耦合(不直接new一個SqlSession)

首先是使用dom4j解析配置文件,具體是使用XMLConfigBuilder.parseConfig(InputStream in)實現的
將sqlMapConfig.xml生成Configuration

1.7 解析映射配置文件mapper.xml

在XMLConfigBuilder裡面,調用XMLMapperBuilder的parse方法,讀取XXXMapper.xml生成MapperStatement,並將其放入到Configuration的mapperStatementMap裡面

1.8 會話對象sqlSession類定義

在回話對象中,使用Configuration裡面儲存的轉換後的配置文件資訊,完成與資料庫的連接及數據獲取操作

1.9 會話對象sqlSession方法定義

以selectList()為例,需要執行以下三步:

1 初始化Executor對象
2 獲取Comfiguration裡面存的xxxMapper.xml轉換後的MapperStatement
3 執行Executor的query方法

1.10 查詢對象query方法定義

一共需要執行六步:

1 註冊驅動,獲取連接
2 獲取sql語句,然後轉換sql語句,此時需要配置標記解析器來對佔位符的解析處理工作(這裡直接用了Mybatis的程式碼)
3 獲取預處理對象:preparedStatement
4 設置參數
5 執行sql
6 封裝返回結果集

注意:
在mapper.xml配置時,不直接寫成?的原因是,為了能夠將根據屬性名直接獲取傳入對象的對應屬性
面向對象的思想,將參數封裝成對象,再返回,具體見buildParameterMapping,不是將String類型的content直接放入List,而是轉換成了ParameterMapping對象放入List

1.11 參數設置實現

使用反射,根據參數名稱,獲取實體對象的屬性值,然後獲取對象中的具體參數值

1.12 封裝返回結果集實現

使用反射或者內省,根據資料庫表和實體的對應關係,完成封裝

1.13 Client端運行測試

如何使用IPErsistence框架:
首先將IPersistence類打包,然後在IPersistence_test的pom引入包即可

UML類圖(重要,需要好好理解):

1.14 功能擴展——getMapper方法實現

自定義持久層框架存在的問題:

1 程式碼重複。Dao層載入配置文件、獲取SqlSessionFactory、生產sqlSession對象存在程式碼重複
2 硬編碼問題。硬編碼了statementId

解決思路:

不要Dao的實現類,使用代理模式實現Dao層介面的代理實現類

具體步驟:

1 在SqlSession創建getMappper,並在DefaultSession重寫
3 在Client端直接調用

1.15 功能擴展——動態代理invoke方法實現

注意:不論如何簡潔、優化,最終還是要執行底層的方法

xxxMapper.xml文件的規範要遵守,namespace要和介面全限定名一致,方法名也要和介面方法名一致

原因:

invoke方法中是沒有辦法獲取statementId的(前面採用的方式是硬編碼寫進去的),但是可以通過介面全限定名.方法名即namespace.id,就組裝出了statementId