【筆記】拉勾Java工程師高薪訓練營-第一階段 開源框架源碼解析-模組一 持久層框架涉及實現及MyBatis源碼分析-任務一:自定義持久層框架
- 2020 年 11 月 12 日
- 筆記
- Java高薪訓練營筆記
以下筆記是我看完影片之後總結整理的,部分較為基礎的知識點也做了補充,如有問題歡迎溝通。
- 任務一:自定義持久層框架
任務一:自定義持久層框架
1.1 JDBC回顧及問題分析
JDBC問題分析:
問題 | 解決方案 |
---|---|
1 資料庫配置資訊存在硬編碼 | 使用配置文件 |
2 頻繁創建釋放資料庫連接 | 使用連接池 |
3 SQL語句、設置參數、獲取結果參數存在硬編碼問題 | 使用配置文件 |
4 手動封裝返回結果集,較為繁瑣 | 使用反射、內省 |
注意:雖然有兩個配置文件,但不建議放到一個文件中,因為一個是經常修改的,一個是幾乎不變的,而且邏輯上也不合理,只不過可以通過特殊方式不用手動載入每一個xml文件,具體見1.3.2 sqlMapConfig.xml詳解
1.2 自定義持久層框架思路分析
使用端:(項目)引入自定義持久層框架的jar包
提供兩部分配置資訊:資料庫配置資訊、SQL配置資訊(SQL語句、參數類型、返回值類型)
- sqlMapConfig.xml:存放資料庫配置資訊
- mapper.xml:存放sql配置資訊
自定義框架層本身:(工程)本質就是對JDBC程式碼進行封裝
- 載入配置文件:根據配置文件路徑,載入配置文件成位元組輸入流,存儲在記憶體中
創建Resource類,方法:InputStream getResourceAsStream(String path) - 創建兩個JavaBean(容器對象),存放的就是配置文件解析出來的內容
Configuration:核心配置類,存放sqlMapConfig.xml解析出來的內容
MapperStatement:映射配置類,存放mapper.xml解析出來的內容 - 解析配置文件:dom4j
創建類:SqlSessionFactoryBuilder包含方法build(InputStream in)主要做兩件事:
3.1 使用dom4j解析配置文件,將解析出來的內容封裝到容器對象中
3.2 創建sqlSessionFactory對象,生產SqlSession,即會話對象(增刪改查都封裝在這裡),這裡用到了工廠模式,降低程式間的耦合(不直接new一個SqlSession) - 創建SqlSessionFactory介面及實現類DefaultSqlSessionFactory,方法openSession()用以生產sqlSession
- 創建SqlSession介面及實現類DefaultSession,定義對資料庫的CRUD操作(selectList、selectOne、update……)
- 創建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中主要完成以下功能:
- 配置真正需要在MySql裡面執行的sql語句
- 實現特定語句的定位,也就是需要給sql一個特定標識,即statementId
- 指定返回結果的封裝類型
- 指定傳入參數的類型
但是這麼寫只能操作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