java中提到持久層框架,相信沒(méi)有人不知道m(xù)ybatis的存在蜈项,相對(duì)于JDBC她多了一份干練(jdbc工作量大),相對(duì)于Hibernate她又多了一份靈動(dòng)(HQL雖然方便,但臺(tái)呆板)锋谐,面對(duì)如此尤物,今天我們就一起走就她的世界截酷。
一涮拗、mybatis的簡(jiǎn)單實(shí)現(xiàn)
→準(zhǔn)備工作
? ? ? ? 1.創(chuàng)建實(shí)體類和表映射
? ? ? ? 2.導(dǎo)入maven依賴
→編寫接口和mapper為文件
注意:在idea中,直接把資源文件放在src文件夾下迂苛,如果不進(jìn)行設(shè)置三热,是不能被找到的,所以一定要在pom.xml(第一張圖)中加入標(biāo)記的代碼三幻,指定加載位置就漾。
→編寫配置文件
注意:配置文件要放在resources目錄下
→測(cè)試
二、mybatis的實(shí)現(xiàn)原理分析
→原理分析之一:MyBatis的主要成員
Configuration :MyBatis所有的配置信息都保存在Configuration對(duì)象之中念搬,配置文件中的大部分配置都會(huì)存儲(chǔ)到該類中
SqlSession :作為MyBatis工作的主要頂層API从藤,表示和數(shù)據(jù)庫(kù)交互時(shí)的會(huì)話,完成必要數(shù)據(jù)庫(kù)增刪改查功能
Executor: MyBatis執(zhí)行器锁蠕,是MyBatis 調(diào)度的核心夷野,負(fù)責(zé)SQL語(yǔ)句的生成和查詢緩存的維護(hù)
StatementHandler :封裝了JDBC Statement操作,負(fù)責(zé)對(duì)JDBC statement 的操作荣倾,如設(shè)置參數(shù)等
ParameterHandler :負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所對(duì)應(yīng)的數(shù)據(jù)類型
ResultSetHandler : 負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類型的集合
TypeHandler :負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型(也可以說(shuō)是數(shù)據(jù)表列類型)之間的映射和轉(zhuǎn)換
MappedStatement :MappedStatement維護(hù)一條<select|update|delete|insert>節(jié)點(diǎn)的封裝
SqlSource:負(fù)責(zé)根據(jù)用戶傳遞的parameterObject悯搔,動(dòng)態(tài)地生成SQL語(yǔ)句,將信息封裝到BoundSql對(duì)象中舌仍,并返回
BoundSql :表示動(dòng)態(tài)生成的SQL語(yǔ)句以及相應(yīng)的參數(shù)信息
→原理分析之二:Mybatis的執(zhí)行流程
→原理分析之三:初始化(配置文件讀取和解析)
1. 準(zhǔn)備工作
? 編寫測(cè)試代碼(具體請(qǐng)參考《Mybatis入門示例》),設(shè)置斷點(diǎn),以Debug模式運(yùn)行,具體代碼如下:
Java代碼?
String resource =?"mybatis.cfg.xml";??Reader reader = Resources.getResourceAsReader(resource);??SqlSessionFactory ssf =?new?SqlSessionFactoryBuilder().build(reader);??SqlSession session = ssf.openSession();
2.源碼分析
我們此次就對(duì)上面的代碼進(jìn)行跟蹤和分析妒貌,let's go。
首先我們按照順序先看看第一行和第二行代碼铸豁,看看它主要完成什么事情:
Java代碼?
String resource =?"mybatis.cfg.xml";??Reader reader = Resources.getResourceAsReader(resource);
讀取Mybaits的主配置配置文件灌曙,并返回該文件的輸入流,我們知道Mybatis所有的SQL語(yǔ)句都寫在XML配置文件里面,所以第一步就需要讀取這些XML配置文件节芥,這個(gè)不難理解在刺,關(guān)鍵是讀取文件后怎么存放逆害。
我們接著看第三行代碼(如下),該代碼主要是讀取配置文件流并將這些配置信息存放到Configuration類中蚣驼。
Java代碼?
SqlSessionFactory ssf =?new?SqlSessionFactoryBuilder().build(reader);
SqlSessionFactoryBuilder的build的方法如下:
Java代碼?
public?SqlSessionFactory build(Reader reader) {??return?build(reader,?null,?null);??}
其實(shí)是調(diào)用該類的另一個(gè)build方法來(lái)執(zhí)行的,具體代碼如下:
Java代碼?
public?SqlSessionFactory build(Reader reader, String environment, Properties properties) {??try?{??XMLConfigBuilder parser =?new?XMLConfigBuilder(reader, environment, properties);??return?build(parser.parse());??}?catch?(Exception e) {??throw?ExceptionFactory.wrapException("Error building SqlSession.", e);??}?finally?{??ErrorContext.instance().reset();??try?{??reader.close();??}?catch?(IOException e) {??// Intentionally ignore. Prefer previous error.??}??}??}
我們重點(diǎn)看一下里面兩行:
Java代碼?
//創(chuàng)建一個(gè)配置文件流的解析對(duì)象XMLConfigBuilder,其實(shí)這里是將環(huán)境和配置文件流賦予解析類??XMLConfigBuilder parser =?new?XMLConfigBuilder(reader, environment, properties);??// 解析類對(duì)配置文件進(jìn)行解析并將解析的內(nèi)容存放到Configuration對(duì)象中,并返回SqlSessionFactory??return?build(parser.parse());
這里的XMLConfigBuilder初始化其實(shí)調(diào)用的代碼如下:
Java代碼?
private?XMLConfigBuilder(XPathParser parser, String environment, Properties props) {??super(new?Configuration());??ErrorContext.instance().resource("SQL Mapper Configuration");??this.configuration.setVariables(props);??this.parsed =?false;??this.environment = environment;??this.parser = parser;??}
XMLConfigBuilder的parse方法執(zhí)行代碼如下:?
Java代碼?
public?Configuration parse() {??if?(parsed) {??throw?new?BuilderException("Each MapperConfigParser can only be used once.");??}??parsed =?true;??parseConfiguration(parser.evalNode("/configuration"));??return?configuration;??}
解析的內(nèi)容主要是在parseConfiguration方法中,它主要完成的工作是讀取配置文件的各個(gè)節(jié)點(diǎn),然后將這些數(shù)據(jù)映射到內(nèi)存配置對(duì)象Configuration中,我們看一下parseConfiguration方法內(nèi)容:
Java代碼?
private?void?parseConfiguration(XNode root) {??try?{??typeAliasesElement(root.evalNode("typeAliases"));??pluginElement(root.evalNode("plugins"));??objectFactoryElement(root.evalNode("objectFactory"));??objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));??propertiesElement(root.evalNode("properties"));??settingsElement(root.evalNode("settings"));??environmentsElement(root.evalNode("environments"));??typeHandlerElement(root.evalNode("typeHandlers"));??mapperElement(root.evalNode("mappers"));??}?catch?(Exception e) {??throw?new?BuilderException("Error parsing SQL Mapper Configuration. Cause: "?+ e, e);??}??}
最后的build方法其實(shí)是傳入配置對(duì)象進(jìn)去,創(chuàng)建DefaultSqlSessionFactory實(shí)例出來(lái). DefaultSqlSessionFactory是SqlSessionFactory的默認(rèn)實(shí)現(xiàn).
Java代碼?
public?SqlSessionFactory build(Configuration config) {??return?new?DefaultSqlSessionFactory(config);??}
最后我們看一下第四行代碼:
Java代碼?
SqlSession session = ssf.openSession();
通過(guò)調(diào)用DefaultSqlSessionFactory的openSession方法返回一個(gè)SqlSession實(shí)例,我們看一下具體是怎么得到一個(gè)SqlSession實(shí)例的魄幕。首先調(diào)用openSessionFromDataSource方法。?
Java代碼?
public?SqlSession openSession() {??return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);??}
下面我們看一下openSessionFromDataSource方法的邏輯:?
Java代碼?
private?SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,?boolean?autoCommit) {??Connection connection =?null;??try?{??//獲取配置信息里面的環(huán)境信息颖杏,這些環(huán)境信息都是包括使用哪種數(shù)據(jù)庫(kù)纯陨,連接數(shù)據(jù)庫(kù)的信息,事務(wù)??final?Environment environment = configuration.getEnvironment();??//根據(jù)環(huán)境信息關(guān)于數(shù)據(jù)庫(kù)的配置獲取數(shù)據(jù)源??final?DataSource dataSource = getDataSourceFromEnvironment(environment);??//根據(jù)環(huán)境信息關(guān)于事務(wù)的配置獲取事務(wù)工廠??TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);??connection = dataSource.getConnection();??if?(level !=?null) {??//設(shè)置連接的事務(wù)隔離級(jí)別??connection.setTransactionIsolation(level.getLevel());??}??//對(duì)connection進(jìn)行包裝留储,使連接具備日志功能翼抠,這里用的是代理。??connection = wrapConnection(connection);??//從事務(wù)工廠獲取一個(gè)事務(wù)實(shí)例??Transaction tx = transactionFactory.newTransaction(connection, autoCommit);??//從配置信息中獲取一個(gè)執(zhí)行器實(shí)例??Executor executor = configuration.newExecutor(tx, execType);??//返回SqlSession的一個(gè)默認(rèn)實(shí)例??return?new?DefaultSqlSession(configuration, executor, autoCommit);??}?catch?(Exception e) {??closeConnection(connection);??throw?ExceptionFactory.wrapException("Error opening session. Cause: "?+ e, e);??}?finally?{??ErrorContext.instance().reset();??}??}
傳入?yún)?shù)說(shuō)明:
(1)ExecutorType:執(zhí)行類型获讳,ExecutorType主要有三種類型:SIMPLE, REUSE, BATCH阴颖,默認(rèn)是SIMPLE,都在枚舉類ExecutorType里面赔嚎。
(2)TransactionIsolationLevel:事務(wù)隔離級(jí)別膘盖,都在枚舉類TransactionIsolationLevel中定義胧弛。
(3)autoCommit:是否自動(dòng)提交尤误,主要是事務(wù)提交的設(shè)置。
DefaultSqlSession是SqlSession的實(shí)現(xiàn)類结缚,該類主要提供操作數(shù)據(jù)庫(kù)的方法給開(kāi)發(fā)人員使用损晤。
這里總結(jié)一下上面的過(guò)程,總共由三個(gè)步驟:
步驟一:讀取Ibatis的主配置文件红竭,并將文件讀成文件流形式(InputStream)尤勋。
步驟二:從主配置文件流中讀取文件的各個(gè)節(jié)點(diǎn)信息并存放到Configuration對(duì)象中。讀取mappers節(jié)點(diǎn)的引用文件茵宪,并將這些文件的各個(gè)節(jié)點(diǎn)信息存放到Configuration對(duì)象最冰。
步驟三:根據(jù)Configuration對(duì)象的信息獲取數(shù)據(jù)庫(kù)連接,并設(shè)置連接的事務(wù)隔離級(jí)別等信息稀火,將經(jīng)過(guò)包裝數(shù)據(jù)庫(kù)連接對(duì)象SqlSession接口返回暖哨,DefaultSqlSession是SqlSession的實(shí)現(xiàn)類,所以這里返回的是DefaultSqlSession凰狞,SqlSession接口里面就是對(duì)外提供的各種數(shù)據(jù)庫(kù)操作篇裁。
原理分析之四:一次SQL查詢的源碼分析
上回我們講到Mybatis加載相關(guān)的配置文件進(jìn)行初始化,這回我們講一下一次SQL查詢?cè)趺催M(jìn)行的赡若。
準(zhǔn)備工作
Mybatis完成一次SQL查詢需要使用的代碼如下:
Java代碼? ?
String resource =?"mybatis.cfg.xml";??Reader reader = Resources.getResourceAsReader(resource);??SqlSessionFactory ssf =?new?SqlSessionFactoryBuilder().build(reader);?? SqlSession session = ssf.openSession();??try?{??UserInfo user = (UserInfo) session.selectOne("User.selectUser",?"1");??System.out.println(user);??}?catch?(Exception e) {??e.printStackTrace();??}?finally?{??session.close();??}
本次我們需要進(jìn)行深入跟蹤分析的是:
Java代碼?
SqlSession session = ssf.openSession();??UserInfo user = (UserInfo) session.selectOne("User.selectUser",?"1");
源碼分析
第一步:打開(kāi)一個(gè)會(huì)話达布,我們看看里面具體做了什么事情。
Java代碼?
SqlSession session = ssf.openSession();
DefaultSqlSessionFactory的 openSession()方法內(nèi)容如下:
Java代碼?
public?SqlSession openSession() {??return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);??}
跟進(jìn)去逾冬,我們看一下openSessionFromDataSource方法到底做了啥:
Java代碼?
private?SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,?boolean?autoCommit) {??Connection connection =?null;??try?{??final?Environment environment = configuration.getEnvironment();??final?DataSource dataSource = getDataSourceFromEnvironment(environment);??TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);??connection = dataSource.getConnection();??if?(level !=?null) {??connection.setTransactionIsolation(level.getLevel());??}?connection = wrapConnection(connection);??Transaction tx = transactionFactory.newTransaction(connection, autoCommit);??Executor executor = configuration.newExecutor(tx, execType);??return?new?DefaultSqlSession(configuration, executor, autoCommit);??}?catch(Exception e) {??closeConnection(connection);??throw?ExceptionFactory.wrapException("Error opening session. Cause: "?+ e, e);??}?finally?{??ErrorContext.instance().reset();?}??}
這里我們分析一下這里所涉及的步驟:
(1)獲取前面我們加載配置文件的環(huán)境信息黍聂,并且獲取環(huán)境信息中配置的數(shù)據(jù)源。
(2)通過(guò)數(shù)據(jù)源獲取一個(gè)連接,對(duì)連接進(jìn)行包裝代理(通過(guò)JDK的代理來(lái)實(shí)現(xiàn)日志功能)分冈。
(3)設(shè)置連接的事務(wù)信息(是否自動(dòng)提交圾另、事務(wù)級(jí)別),從配置環(huán)境中獲取事務(wù)工廠雕沉,事務(wù)工廠獲取一個(gè)新的事務(wù)集乔。
(4)傳入事務(wù)對(duì)象獲取一個(gè)新的執(zhí)行器,并傳入執(zhí)行器坡椒、配置信息等獲取一個(gè)執(zhí)行會(huì)話對(duì)象扰路。
從上面的代碼我們可以得出,一次配置加載只能有且對(duì)應(yīng)一個(gè)數(shù)據(jù)源倔叼。對(duì)于上述步驟汗唱,我們不難理解,我們重點(diǎn)看看新建執(zhí)行器和DefaultSqlSession丈攒。
首先哩罪,我們看看newExecutor到底做了什么?
Java代碼?
public?Executor newExecutor(Transaction transaction, ExecutorType executorType) {?executorType = executorType ==?null?? defaultExecutorType : executorType;??executorType = executorType ==?null?? ExecutorType.SIMPLE : executorType;??Executor executor;??if?(ExecutorType.BATCH == executorType) {??executor =?new?BatchExecutor(this, transaction);??}?else?if?(ExecutorType.REUSE == executorType) {??executor =?new?ReuseExecutor(this, transaction);??}?else?{??executor =?new?SimpleExecutor(this, transaction);??}??if?(cacheEnabled) {??executor =?new?CachingExecutor(executor);??}??executor = (Executor) interceptorChain.pluginAll(executor);??return?executor;??}
上面代碼的執(zhí)行步驟如下:
(1)判斷執(zhí)行器類型巡验,如果配置文件中沒(méi)有配置執(zhí)行器類型际插,則采用默認(rèn)執(zhí)行類型ExecutorType.SIMPLE。
(2)根據(jù)執(zhí)行器類型返回不同類型的執(zhí)行器(執(zhí)行器有三種显设,分別是 BatchExecutor框弛、SimpleExecutor和CachingExecutor,后面我們?cè)僭敿?xì)看看)捕捂。
(3)跟執(zhí)行器綁定攔截器插件(這里也是使用代理來(lái)實(shí)現(xiàn))瑟枫。
DefaultSqlSession到底是干什么的呢?
DefaultSqlSession實(shí)現(xiàn)了SqlSession接口指攒,里面有各種各樣的SQL執(zhí)行方法慷妙,主要用于SQL操作的對(duì)外接口,它會(huì)的調(diào)用執(zhí)行器來(lái)執(zhí)行實(shí)際的SQL語(yǔ)句允悦。
接下來(lái)我們看看SQL查詢是怎么進(jìn)行的
Java代碼?
UserInfo user = (UserInfo) session.selectOne("User.selectUser",?"1");
實(shí)際調(diào)用的是DefaultSqlSession類的selectOne方法膝擂,該方法代碼如下:
Java代碼?
public?Object selectOne(String statement, Object parameter) {??// Popular vote was to return null on 0 results and throw exception on too many.??List list = selectList(statement, parameter);??if?(list.size() ==?1) {??return?list.get(0);??}?else?if?(list.size() >?1) {??throw?new?TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: "?+ list.size());??}?else?{??return?null;??}??}
我們?cè)倏纯磗electList方法(實(shí)際上是調(diào)用該類的另一個(gè)selectList方法來(lái)實(shí)現(xiàn)的):
Java代碼?
public?List selectList(String statement, Object parameter) {??return?selectList(statement, parameter, RowBounds.DEFAULT);??}??public?List selectList(String statement, Object parameter, RowBounds rowBounds) {??try?{??MappedStatement ms = configuration.getMappedStatement(statement);??return?executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);??}?catch?(Exception e) {??throw?ExceptionFactory.wrapException("Error querying database. Cause: "?+ e, e);??}?finally?{??ErrorContext.instance().reset();??}??}
第二個(gè)selectList的執(zhí)行步驟如下:
(1)根據(jù)SQL的ID到配置信息中找對(duì)應(yīng)的MappedStatement,在之前配置被加載初始化的時(shí)候我們看到了系統(tǒng)會(huì)把配置文件中的SQL塊解析并放到一個(gè)MappedStatement里面澡屡,并將MappedStatement對(duì)象放到一個(gè)Map里面進(jìn)行存放猿挚,Map的key值是該SQL塊的ID。
(2)調(diào)用執(zhí)行器的query方法驶鹉,傳入MappedStatement對(duì)象绩蜻、SQL參數(shù)對(duì)象、范圍對(duì)象(此處為空)和結(jié)果處理方式室埋。
好了办绝,目前只剩下一個(gè)疑問(wèn)伊约,那就是執(zhí)行器到底怎么執(zhí)行SQL的呢?
上面我們知道了孕蝉,默認(rèn)情況下是采用SimpleExecutor執(zhí)行的屡律,我們看看這個(gè)類的doQuery方法:
Java代碼?
public?List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)?throws?SQLException {??Statement stmt =?null;??try?{??Configuration configuration = ms.getConfiguration();??StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler);??stmt = prepareStatement(handler);??return?handler.query(stmt, resultHandler);??}?finally?{??closeStatement(stmt);??}??}
doQuery方法的內(nèi)部執(zhí)行步驟:
(1) 獲取配置信息對(duì)象。
(2)通過(guò)配置對(duì)象獲取一個(gè)新的StatementHandler降淮,該類主要用來(lái)處理一次SQL操作超埋。
(3)預(yù)處理StatementHandler對(duì)象,得到Statement對(duì)象佳鳖。
(4)傳入Statement和結(jié)果處理對(duì)象霍殴,通過(guò)StatementHandler的query方法來(lái)執(zhí)行SQL,并對(duì)執(zhí)行結(jié)果進(jìn)行處理系吩。
我們看一下newStatementHandler到底做了什么来庭?
Java代碼?
public?StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {??StatementHandler statementHandler =?new?RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);??statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);??return?statementHandler;??}
上面代碼的執(zhí)行步驟:
(1)根據(jù)相關(guān)的參數(shù)獲取對(duì)應(yīng)的StatementHandler對(duì)象。
(2)為StatementHandler對(duì)象綁定攔截器插件穿挨。
RoutingStatementHandler類的構(gòu)造方法RoutingStatementHandler如下:
Java代碼?
public?RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {??switch?(ms.getStatementType()) {??case?STATEMENT:??delegate =?new?SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler);??break;??case?PREPARED:??delegate =?new?PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler);??break;??case?CALLABLE:??delegate =?new?CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler);??break;??default:??throw?new?ExecutorException("Unknown statement type: "?+ ms.getStatementType());??}??}
根據(jù) MappedStatement對(duì)象的StatementType來(lái)創(chuàng)建不同的StatementHandler月弛,這個(gè)跟前面執(zhí)行器的方式類似。StatementType有STATEMENT科盛、PREPARED和CALLABLE三種類型帽衙,跟JDBC里面的Statement類型一一對(duì)應(yīng)。
我們看一下prepareStatement方法具體內(nèi)容:
Java代碼?
private?Statement prepareStatement(StatementHandler handler)?throws?SQLException {?Statement stmt;??Connection connection = transaction.getConnection();??//從連接中獲取Statement對(duì)象??stmt = handler.prepare(connection);??//處理預(yù)編譯的傳入?yún)?shù)??handler.parameterize(stmt);??return?stmt;??}