架構(gòu)設(shè)計(jì)
我們把Mybatis的功能架構(gòu)分為三層:
(1) API接口層:提供給外部使用的接口 API星澳,開發(fā)人員通過這些本地API來操縱數(shù)據(jù)庫吁讨。接口層一接收
到 調(diào)用請(qǐng)求就會(huì)調(diào)用數(shù)據(jù)處理層來完成具體的數(shù)據(jù)處理髓迎。
MyBatis和數(shù)據(jù)庫的交互有兩種方式:
a. 使用傳統(tǒng)的MyBati s提供的API ;
b. 使用Mapper代理的方式
(2) 數(shù)據(jù)處理層:負(fù)責(zé)具體的SQL查找建丧、SQL解析排龄、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等。它主要的目的是根
據(jù)調(diào)用的請(qǐng)求完成一次數(shù)據(jù)庫操作翎朱。
(3) 基礎(chǔ)支撐層:負(fù)責(zé)最基礎(chǔ)的功能支撐橄维,包括連接管理、事務(wù)管理拴曲、配置加載和緩存處理争舞,這些都是
共 用的東西,將他們抽取出來作為最基礎(chǔ)的組件疗韵。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐
大致流程如下
主要構(gòu)件及其相互關(guān)系如下:
源碼剖析
1.加載配置文件流程
在通過SqlSessionfactoryBuilder創(chuàng)建SqlSessionfactory時(shí)候會(huì)先將配置文件轉(zhuǎn)換成文件流并且解析后存放在configuration對(duì)象中
可以看到通過XNLconfigBuilder中parse解析xml文件流到configuration中,parse方法如下(Configuration中主要存放數(shù)據(jù)庫鏈接信息配置信息以及sql相關(guān)信息具體可以查閱Configuration)
parseConfiguration方法如下,其中</mapper>下存放的就是
解析完成后會(huì)返回一個(gè)含有一個(gè)configuration的SQlsessionfactory工廠類?用來生成sqlsession
2.生成sqlSession流程
整體流程大致如下;
主要看一下創(chuàng)建 Executor 對(duì)象過程(畢竟執(zhí)行sql的主要接口,大多數(shù)操作實(shí)際都是在該類中完成的)
主要注意的就是如果開啟了二級(jí)緩存就會(huì)使用CachingExecutor類?沒有開啟就是要用SimpleExecutor
sql執(zhí)行過程
這里以sqlsession,selectList()作為例子進(jìn)行演示
selectList源碼如下
這里以沒有開啟二級(jí)緩存代碼進(jìn)行分析query方法?這里主要生成兩個(gè)對(duì)象在執(zhí)行數(shù)據(jù)查詢前
BoundSql:存放了sql信息?cacheKey存放了緩存的key
獲取到key和boundSQL后?后續(xù)操作?涉及到一級(jí)緩存?具體流程可以參考一級(jí)緩存的介紹,這里主要看從數(shù)據(jù)庫查詢過程
實(shí)際數(shù)據(jù)查詢的操作是在queryFromDatabase中?流程如下
StatementHandler主要是封裝了JDBC Statement操作侄非,負(fù)責(zé)對(duì)JDBC statement的操作蕉汪,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合逞怨。
可以看出這里也是可以使用插件的
handler.prepare(connection, transaction.getTimeout())用來生成Statement對(duì)象?也是對(duì)數(shù)據(jù)sql查詢的核心
設(shè)置 SQL 上的參數(shù)者疤,例如 PrepareStatement 對(duì)象上的占位符handler.parameterize(stmt);如?的替換?主要用到了反射實(shí)現(xiàn)而這個(gè)具體過程交由了TypeHander處理
包含了各種類型的參數(shù)的處理實(shí)現(xiàn)類
sql處理完成后交由Statement來執(zhí)行?返回的結(jié)果集交由resultSetHandler來處理
getMapper代理方式
首先使用mapper的寫法如下:
開始之前介紹一下MyBatis初始化時(shí)對(duì)接口的處理:MapperRegistry是Configuration中的一個(gè)屬性,它內(nèi)部維護(hù)一個(gè)HashMap用于存放mapper接口的工廠類叠赦,每個(gè)接口對(duì)應(yīng)一個(gè)工廠類驹马。mappers中可以配置接口的包路徑,或者某個(gè)具體的接口類除秀。
當(dāng)解析mappers標(biāo)簽時(shí)糯累,它會(huì)判斷解析到的是mapper配置文件時(shí),會(huì)再將對(duì)應(yīng)配置文件中的增刪 改查標(biāo)簽 封裝成MappedStatement對(duì)象册踩,存入mappedStatements中泳姐。(上文介紹了)當(dāng)判斷解析到接口時(shí),會(huì)建此接口對(duì)應(yīng)的MapperProxyFactory對(duì)象暂吉,存入HashMap中胖秒,key =接口的字節(jié)碼對(duì)象,value =此接口對(duì)應(yīng)的MapperProxyFactory對(duì)象慕的。
源碼剖析-getmapper()進(jìn)入 sqlSession.getMapper(UserMapper.class )中
繼承了INcocationhandler實(shí)現(xiàn)invoke方法
最終執(zhí)行的還是sqlSession方法呀
public Objectexecute(SqlSession sqlSession, Object[] args) {
Object result;
? ? //判斷mapper中的方法類型阎肝,最終調(diào)用的還是SqlSession中的方法
? ? switch (command.getType()) {
case INSERT: {
// 轉(zhuǎn)換參數(shù)
? ? ? ? ? ? Object param =method.convertArgsToSqlCommandParam(args);
? ? ? ? ? ? // 執(zhí)行 INSERT 操作
? ? ? ? ? ? // 轉(zhuǎn)換rowCount
? ? ? ? ? ? result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
? ? ? ? }
case UPDATE: {
// 轉(zhuǎn)換參數(shù)
? ? ? ? ? ? Object param =method.convertArgsToSqlCommandParam(args);
? ? ? ? ? ? // 轉(zhuǎn)換rowCount
? ? ? ? ? ? result = rowCountResult(sqlSession.update(command.getName(), param));
break;
? ? ? ? }
case DELETE: {
// 轉(zhuǎn)換參數(shù)
? ? ? ? ? ? Object param =method.convertArgsToSqlCommandParam(args);
? ? ? ? ? ? // 轉(zhuǎn)換rowCount
? ? ? ? ? ? result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
? ? ? ? }
case SELECT:
// 無返回,并且有 ResultHandler 方法參數(shù)肮街,則將查詢的結(jié)果风题,提交給 ResultHandler 進(jìn)行處理
? ? ? ? ? ? if (method.returnsVoid() &&method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
? ? ? ? ? ? ? ? result =null;
? ? ? ? ? ? // 執(zhí)行查詢,返回列表
? ? ? ? ? ? }else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
? ? ? ? ? ? // 執(zhí)行查詢,返回Map
? ? ? ? ? ? }else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
? ? ? ? ? ? // 執(zhí)行查詢俯邓,返回Cursor
? ? ? ? ? ? }else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
? ? ? ? ? ? // 執(zhí)行查詢骡楼,返回單個(gè)對(duì)象
? ? ? ? ? ? }else {
// 轉(zhuǎn)換參數(shù)
? ? ? ? ? ? ? ? Object param =method.convertArgsToSqlCommandParam(args);
? ? ? ? ? ? ? ? // 查詢單條
? ? ? ? ? ? ? ? result = sqlSession.selectOne(command.getName(), param);
? ? ? ? ? ? ? ? if (method.returnsOptional() &&
(result ==null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
? ? ? ? ? ? ? ? }
}
break;
? ? ? ? case FLUSH:
result = sqlSession.flushStatements();
break;
? ? ? ? default:
throw new BindingException("Unknown execution method for: " +command.getName());
? ? }
// 返回結(jié)果為 null ,并且返回類型為基本類型稽鞭,則拋出 BindingException 異常
? ? if (result ==null &&method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" +command.getName()
+" attempted to return null from a method with a primitive return type (" +method.getReturnType() +").");
? ? }
// 返回結(jié)果
? ? return result;
}
哈哈