從Mybatis源碼到Spring動態(tài)數(shù)據(jù)源底層原理分析、Mybatis初始化源碼淺析
一、引入
本系列文章最終的目標是為了分析spring動態(tài)數(shù)據(jù)源的原理, 相信大家在公司的開發(fā)中應該都會遇到一個項目中出現(xiàn)多個數(shù)據(jù)庫連接的情況, 以我遇到的一種情況來說, 用戶根據(jù)id分布在不同的數(shù)據(jù)庫, 每個數(shù)據(jù)庫中的表結(jié)構(gòu)一模一樣, 這就會導致, 在執(zhí)行業(yè)務代碼的時候, 不同的用戶需求在不同的數(shù)據(jù)庫中執(zhí)行對應的sql, 即在mybatis作為持久層框架的情況下, 一個mapper可能會在不同的情況下, 對不同的數(shù)據(jù)庫進行操作, 這就需要我們有能夠執(zhí)行動態(tài)數(shù)據(jù)源的功能, 而在這其中, 最重要的一部分就是事務了(不是分布式事務, 應該屬于動態(tài)事務), @Transactional中標注的業(yè)務邏輯, 在不同的數(shù)據(jù)源下需要執(zhí)行不同數(shù)據(jù)庫的事務, 為了能夠清晰的了解到spring-mybatis是如何聯(lián)動實現(xiàn)動態(tài)數(shù)據(jù)源, 以及實現(xiàn)動態(tài)事務的原理, 我們從mybatis的源碼開始分析, 到spring整合mybatis的原理, 到spring事務原理, 最終來展示動態(tài)數(shù)據(jù)源情況下動態(tài)事務的原理
注意事項, mybatis源碼相對于之前我分析的spring、springmvc的源碼來說, 會更加的簡單, 但是我不會把每一個細節(jié)都分析的非常底層, 而是從一個整體的流程出發(fā)進行分析, 在對mybatis有一個整體的認知之后, 如果想深入的了解某一塊的內(nèi)容, 那么會變得非常輕松, 舉個例子, 對于如何將一個
二、配置文件整體概覽
2.1、簡單的描述
我們以xml格式下的mybatis文件進行分析, 這是相對比較傳統(tǒng)的, 而目前大家都是基于springboot進行開發(fā), 在這種情況下, 只不過是將對mybatis配置文件的解析變成了對application.yml文件的解析, 進而得到對應的mybatis配置對象而已, 之后我們在分析spring整合mybatis的時候, 也會分析這一塊, 從xml文件入手, 我們之后會能夠更加的清晰springboot是如何剔除了mybatis-config這樣的配置文件的
2.2、配置文件
------ mybatis-con.xml ------
configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://dev1-linux.pospal.cn:3306/pospal?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=true&allowMultiQueries=true"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/CustomerMapper.xml"/>
mappers>
configuration>
------ CustomerMapper.xml ------
mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fightzhong.mapper.CustomerMapper">
<select id="selectRemarksById" parameterType="int" resultType="String" >
select remarks
from customer where id = #{id}
select>
mapper>
復制代碼2.3、config文件分析
其實mybatis在初始化的時候就是對上面這些配置文件進行xml解析而已, 將它們解析成一個個的java對象存儲起來, 首先我們來看看mybatis-con.xml這個文件, configuration對象, 故名思義, 就是mybatis的配置, 在mybatis初始化的時候, 會將其解析成一個Configuration對象, 而environment對象也是一樣, 會將其解析成一個Environment對象, environments標簽中可以配置多個environment標簽, 通過default來表示默認生效哪一個, 在environment中, 則表示的是當前環(huán)境下的數(shù)據(jù)庫配置信息, 其中包括事務工廠(用于創(chuàng)建事務的factory)、數(shù)據(jù)源等, 所以以上配置映射成java對象即如下:
class Configuration {
private Environment environment;
private Map mappedStatements;
}
class Environment {
private TransactionFactory transactionFactory;
private DataSource dataSource;
}
復制代碼 非常清晰的映射關系, 一個環(huán)境里面包含了數(shù)據(jù)源以及事務工廠, 如果不是很理解事務工廠的用處, 可以先不用著急, 我們后面的文章會進行詳細的分析, 在本小節(jié), 只是為了告訴大家, 一個mybatis的配置文件, 最終其實是以java對象保存的, 而xml中的標簽層級, 其實就是對應了對象中的屬性層級
2.4、mapper文件分析
對于xxxMapper.xml這些文件, mybatis中通過mapper標簽的namespace + select / update等標簽中的id構(gòu)成一個唯一標識(假設為statementId), 每一個sql標簽以MappedStatement的對象的形式保存在Configuration對象中, 在上面的Configuration中, 以statementId -> MappedStatement 形成一個映射關系, 而MappedStatement中則存儲了一個select等類型的標簽中的所有內(nèi)容, 比如parameterType, resultType, resultMap, sql等, 當我們在觸發(fā)sql執(zhí)行的時候, 即通過statementId找到對應的MappedStatement, 取出里面的sql來執(zhí)行, 然后利用resulthandler以及resultType或者resultMap等信息對結(jié)果集進行封裝映射, 最后返回
在日常的開發(fā)中, 我們面向接口編程, 會定義一個個的Mapper接口類, 當執(zhí)行這些接口方法的時候, 代理對象會利用接口的全路徑類名 + 接口方法名構(gòu)成一個statementId, 進而獲取到對應的MappedStatement, 然后從中提取sql, 執(zhí)行sql, 最后將結(jié)果集利用MappedStatement中的保存的resultMap等信息映射成對應的java對象
2.5、總結(jié)
mybatis-con.xml這樣的配置文件, 以Configuration對象存儲, 表示整個mybatis的配置文件, 里面包含了一個環(huán)境(當前mybatis中對應的數(shù)據(jù)源以及事務工廠)以及所有的Mapper文件中一個個sql標簽解析出來后的MappedStatement對象, xml文件的層次對應了java對象中的屬性層次, MappedStatement中包含了一個sql標簽需要執(zhí)行的所有信息, 包括sql、動態(tài)sql解析需要的數(shù)據(jù)以及通過jdbc查詢到的結(jié)果集如果處理的信息
3、配置文件初始化源碼分析
3.1、代碼引入
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream( "mybatis-con.xml" ) );
SqlSession sqlSession = sqlSessionFactory.openSession();
List通過上面三行代碼, 我們完成了mybatis的初始化, 以及執(zhí)行了上面mapper中的一個select語句, 可以看到, 通過將mybatis-con.xml這個配置文件以流的形式讀取, 然后利用這個文件流開始讀取mybatis的配置, 我們可以先不用理會什么是SqlSession以及SqlSessionFactory, 在mybatis源碼分析的最后, 我才會跟大家說明這兩個東西是什么, 如果沒有對底層依賴的組件有一個清晰的了解, 那么我們也不會深刻的了解到這兩個類的真正作用! 不過SqlSessionFactoryBuilder.build方法卻是我們需要進行分析的, 注意, 不要因為不知道SqlSessionFactoryBuilder和SqlSession是什么而感到煩惱!
3.2、build方法開始構(gòu)建Configuration對象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
}
復制代碼可以看到, build方法調(diào)用到最后, 就是創(chuàng)建了一個XMLConfigBuilder, 然后調(diào)用它的parse方法創(chuàng)建了Configuration對象而已, build方法的參數(shù)inputStream就是配置文件的文件流, environment字段就是表示我們期望生效
3.3、parse方法開始構(gòu)建Configuration對象
public Configuration parse() {
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
}
復制代碼非常清晰的xml解析過程, 最頂層解析configuration標簽, 得到一個root(XNode), 如果有接觸過xml解析, 或者有學過前端dom編程的同學就不會感到陌生, 就像前端的document.getxxx()方法的調(diào)用, 得到根節(jié)點后, 開始解析里面的一個個標簽
properties: 解析properties標簽, 將里面的一個個鍵值對取出來以Properties對象的形式保存到Configuration中
settings: 解析settings標簽, 將一個個的mybatis配置設置到Configuration中, loadCustomVfs、 loadCustomLogImpl都是對settings標簽的解析, settingsElement方法也是, 這個方法就是讀取settings標簽中的一個個配置, 然后調(diào)用configuration.setXXXX()方法進行設置
typeAliases: 別名的解析
plugins: mybatis插件的解析, 如果有使用過mybatis插件, 那么對于這里面的代碼就會比較清晰, 其實就是一個個的攔截器而已, 大家有興趣可以做一下mybatis插件的實踐, 從而能夠更加清楚的認識插件的作用
objectFactory: 在mybatis對sql執(zhí)行完畢之后, 會對結(jié)果集進行處理, 創(chuàng)建一個個的對象并返回, 創(chuàng)建對象就是利用反射完成的, objectFactory即對象工廠, 完成對象反射的創(chuàng)建, 接口方法非常簡單, 就是提供一個Class對象, 然后根據(jù)這個Class對象創(chuàng)建對應的實體類對象, 默認是DefaultObjectFactory, mybatis提供了擴展, 允許開發(fā)者自己定義對象創(chuàng)建的工廠類
objectWrapperFactory: 創(chuàng)建ObjectWrapper的工廠類, ObjectWrapper即對一個對象進行了包裝, 然后利用該類中提供的反射方法對該對象進行反射屬性的操作, 比如setXXX, getXXX (如果有了解過spring源碼的話, 就會發(fā)現(xiàn), 在spring創(chuàng)建bean對象的時候也有一個類似的功能類), 默認的實現(xiàn)類為DefaultObjectWrapperFactory, 里面沒有任何功能, 該類是mybatis提供給開發(fā)者的擴展功能
reflectorFactory: ReflectorFactory工廠的解析, 用于創(chuàng)建Relector對象, 每個Relector對象中包含了一個class對象, 以及這個類中所有的set、get方法, set方法參數(shù)集合、get方法參數(shù)集合, 這個類其實就是反射工具類, 只需要提供一個對應的對象, 然后就能利用Relector完成反射方法的調(diào)用, 同樣有默認的實現(xiàn)DefaultReflectorFactory, 其實ObjectWrapper最還是通過Reflector來完成反射方法的調(diào)用的
ObjectFactory、objectWrapper、Reflector是mybatis底層反射的核心功能類, 提供了完整的反射的功能, 我們這里僅僅是引入這些接口的功能而已, 因為在mybatis中可以通過配置文件提供自定義的實現(xiàn)類, 所以我們簡單的提及一下, 大家有興趣可以深入了解下這些類的功能
environments: 該標簽就是解析當前mybatis中的數(shù)據(jù)源環(huán)境了, 完成了Environment對象的創(chuàng)建(同時完成了TransactionFactory和DataSource的創(chuàng)建, 前者是事務工廠, 用來創(chuàng)建事務的, 后面我們會詳細分析,前面我們也有簡單提到)
databaseIdProvider: 跳過, 不太清楚是干嘛的.....
typeHandlers: 自定義typeHandlers的的解析, typeHandler是用于將jdbc類型和java類型進行映射的關鍵功能類, mybatis為目前java這邊的絕大部分內(nèi)置類型以及與對應的jdbc類型映射關系提供了默認的typeHandler, 存放在typeHandlerRegistry中, 該對象在初始化的同時會創(chuàng)建默認的typeHandler, ?typehandler作用在將java類型轉(zhuǎn)成jdbc類型(即參數(shù)映射)或者將jdbc類型轉(zhuǎn)成java類型(即結(jié)果集映射)中, 之前在項目中剛好有需要將數(shù)據(jù)庫中以逗號分隔的字符串解析成一個List
mappers: 解析一個個的mapper文件, 將mapper文件中的一個個sql標簽解析成MappedStatement對象并存儲在Configuration中, 為了不影響主線, 解析mapper文件的代碼我們不進行深入分析, 之后如果有機會再用文章來描述, 里面涉及到了ResultMap標簽等的解析, 相對比較復雜
4、總結(jié)
我們從Mybatis配置文件、mapper文件出發(fā), 引出了這些xml文件映射到java對象的整體情況, 隨后對mybatis初始化代碼進行了簡單的分析, 其實就是將一個個的dom樹解析成java對象來表示, 至此, 我們知道了Mybatis配置的整體模型:
class Configuration {
// 環(huán)境配置, 表示當前mybatis中生效的數(shù)據(jù)源對象以及事務創(chuàng)建的工具類
private Environment environment;
// 每一個sql標簽的映射關系, namespace + sql標簽id構(gòu)成key, sql標簽中的詳細信息構(gòu)成MappedStatement對象
private Map mappedStatements;
}
class Environment {
// 事務創(chuàng)建工廠工具類
private TransactionFactory transactionFactory;
// 生效的數(shù)據(jù)源
private DataSource dataSource;
}
復制代碼 到此為止, 我們對mybatis的整體結(jié)構(gòu)有了一些認識, 過程中我們接觸到了SqlSessionFactory、SqlSession對象, 如果不清楚這兩個類的功能, 一定不要煩惱, 這個在mybatis源碼分析的最后再來引出這兩個對象的真正作用~!
作者:zhongshenglong
鏈接:https://juejin.cn/post/7021000776205991944
來源:稀土掘金
著作權歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權,非商業(yè)轉(zhuǎn)載請注明出處。
