mybatis返回null_面试官:你分析过mybatis工作原理吗?
Mybatis工作原理也是面試的一大考點(diǎn),必須要對(duì)其非常清晰,這樣才能懟回去。本文建立在Spring+SpringMVC+Mybatis整合的項(xiàng)目之上。
我將其工作原理分為六個(gè)部分:
讀取核心配置文件并返回InputStream流對(duì)象。
根據(jù)InputStream流對(duì)象解析出Configuration對(duì)象,然后創(chuàng)建SqlSessionFactory工廠對(duì)象
根據(jù)一系列屬性從SqlSessionFactory工廠中創(chuàng)建SqlSession
從SqlSession中調(diào)用Executor執(zhí)行數(shù)據(jù)庫(kù)操作&&生成具體SQL指令
對(duì)執(zhí)行結(jié)果進(jìn)行二次封裝
提交與事務(wù)
先給大家看看我的實(shí)體類(lèi):
/**?*?圖書(shū)實(shí)體
?*/
public?class?Book?{
????private?long?bookId;//?圖書(shū)ID
????private?String?name;//?圖書(shū)名稱(chēng)
????private?int?number;//?館藏?cái)?shù)量
????????getter?and?setter?...
}
1. 讀取核心配置文件
1.1 配置文件mybatis-config.xml
<?xml ?version="1.0"?encoding="UTF-8"??>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.jdbc.Driver"/>
????????????????<property?name="url"?value="jdbc:mysql://xxx.xxx:3306/ssm"?/>
????????????????<property?name="username"?value="root"/>
????????????????<property?name="password"?value="root"/>
????????????dataSource>
????????environment>
????environments>
????????<mappers>
????????<mapper?resource="BookMapper.xml"/>
????mappers>
configuration>
當(dāng)然,還有很多可以在XML 文件中進(jìn)行配置,上面的示例指出的則是最關(guān)鍵的部分。要注意 XML 頭部的聲明,用來(lái)驗(yàn)證 XML 文檔正確性。environment 元素體中包含了事務(wù)管理和連接池的配置。mappers 元素則是包含一組 mapper 映射器(這些 mapper 的 XML 文件包含了 SQL 代碼和映射定義信息)。
1.2 BookMapper.xml
<?xml ?version="1.0"?encoding="UTF-8"?>mapper
????PUBLIC?"-//mybatis.org//DTD?Mapper?3.0//EN"
????"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper?namespace="Book">
????
????<insert?id="insert"?>
????????insert?into?book?(name,number)?values?(#{name},#{number})
????insert>
mapper>
就是一個(gè)普通的mapper.xml文件。
1.3 Main方法
從 XML 文件中構(gòu)建 SqlSessionFactory 的實(shí)例非常簡(jiǎn)單,建議使用類(lèi)路徑下的資源文件進(jìn)行配置。但是也可以使用任意的輸入流(InputStream)實(shí)例,包括字符串形式的文件路徑或者 file:// 的 URL 形式的文件路徑來(lái)配置。
MyBatis 包含一個(gè)名叫 Resources 的工具類(lèi),它包含一些實(shí)用方法,可使從 classpath 或其他位置加載資源文件更加容易。
public?class?Main?{????public?static?void?main(String[]?args)?throws?IOException?{
????????//?創(chuàng)建一個(gè)book對(duì)象
????????Book?book?=?new?Book();
????????book.setBookId(1006);
????????book.setName("Easy?Coding");
????????book.setNumber(110);
????????//?加載配置文件?并構(gòu)建SqlSessionFactory對(duì)象
????????String?resource?=?"mybatis-config.xml";
????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
????????SqlSessionFactory?factory?=?new?SqlSessionFactoryBuilder().build(inputStream);
????????//?從SqlSessionFactory對(duì)象中獲取?SqlSession對(duì)象
????????SqlSession?sqlSession?=?factory.openSession();
????????//?執(zhí)行操作
????????sqlSession.insert("insert",?book);
????????//?提交操作
????????sqlSession.commit();
????????//?關(guān)閉SqlSession
????????sqlSession.close();
????}
}
這個(gè)代碼是根據(jù)Mybatis官方提供的一個(gè)不使用 XML 構(gòu)建 SqlSessionFactory的一個(gè)Demo改編的。
注意:是官方給的一個(gè)不使用 XML 構(gòu)建 SqlSessionFactory的例子,那么我們就從這個(gè)例子中查找入口來(lái)分析。
2. 根據(jù)配置文件生成SqlSessionFactory工廠對(duì)象
2.1 Resources.getResourceAsStream(resource);源碼分析
Resources是mybatis提供的一個(gè)加載資源文件的工具類(lèi)。
我們只看getResourceAsStream方法:
public?static?InputStream?getResourceAsStream(String?resource)?throws?IOException?{????return?getResourceAsStream((ClassLoader)null,?resource);
}
getResourceAsStream調(diào)用下面的方法:
public?static?InputStream?getResourceAsStream(ClassLoader?loader,?String?resource)?throws?IOException?{????InputStream?in?=?classLoaderWrapper.getResourceAsStream(resource,?loader);
????if?(in?==?null)?{
????????throw?new?IOException("Could?not?find?resource?"?+?resource);
????}?else?{
????????return?in;
????}
}
獲取到自身的ClassLoader對(duì)象,然后交給ClassLoader(lang包下的)來(lái)加載:
InputStream?getResourceAsStream(String?resource,?ClassLoader[]?classLoader)?{????ClassLoader[]?arr$?=?classLoader;
????int?len$?=?classLoader.length;
????for(int?i$?=?0;?i$?????????ClassLoader?cl?=?arr$[i$];
????????if?(null?!=?cl)?{
????????????InputStream?returnValue?=?cl.getResourceAsStream(resource);
????????????if?(null?==?returnValue)?{
????????????????returnValue?=?cl.getResourceAsStream("/"?+?resource);
????????????}
????????????if?(null?!=?returnValue)?{
????????????????return?returnValue;
????????????}
????????}
????}
值的注意的是,它返回了一個(gè)InputStream對(duì)象。
2.2 new SqlSessionFactoryBuilder().build(inputStream);源碼分析
public?SqlSessionFactoryBuilder()?{}
所以new SqlSessionFactoryBuilder()只是創(chuàng)建一個(gè)對(duì)象實(shí)例,而沒(méi)有對(duì)象返回(建造者模式),對(duì)象的返回交給build()方法。
public?SqlSessionFactory?build(InputStream?inputStream)?{????return?this.build((InputStream)inputStream,?(String)null,?(Properties)null);
}
這里要傳入一個(gè)inputStream對(duì)象,就是將我們上一步獲取到的InputStream對(duì)象傳入。
public?SqlSessionFactory?build(InputStream?inputStream,?String?environment,?Properties?properties)?{????SqlSessionFactory?var5;
????try?{
????????//?進(jìn)行XML配置文件的解析
????????XMLConfigBuilder?parser?=?new?XMLConfigBuilder(inputStream,?environment,?properties);
????????var5?=?this.build(parser.parse());
????}?catch?(Exception?var14)?{
????????throw?ExceptionFactory.wrapException("Error?building?SqlSession.",?var14);
????}?finally?{
????????ErrorContext.instance().reset();
????????try?{
????????????inputStream.close();
????????}?catch?(IOException?var13)?{
????????????;
????????}
????}
????return?var5;
}
如何解析的就大概說(shuō)下,通過(guò)Document對(duì)象來(lái)解析,然后返回InputStream對(duì)象,然后交給XMLConfigBuilder構(gòu)造成org.apache.ibatis.session.Configuration對(duì)象,然后交給build()方法構(gòu)造程SqlSessionFactory:
public?SqlSessionFactory?build(Configuration?config)?{????return?new?DefaultSqlSessionFactory(config);
}
public?DefaultSqlSessionFactory(Configuration?configuration)?{
????this.configuration?=?configuration;
}
3. 創(chuàng)建SqlSession
SqlSession 完全包含了面向數(shù)據(jù)庫(kù)執(zhí)行 SQL 命令所需的所有方法。你可以通過(guò) SqlSession 實(shí)例來(lái)直接執(zhí)行已映射的 SQL 語(yǔ)句。
public?SqlSession?openSession()?{????return?this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(),?(TransactionIsolationLevel)null,?false);
}
調(diào)用自身的openSessionFromDataSource方法:
getDefaultExecutorType()默認(rèn)是SIMPLE。
注意TX等級(jí)是 Null, autoCommit是false。
????Transaction?tx?=?null;
????DefaultSqlSession?var8;
????try?{
????????Environment?environment?=?this.configuration.getEnvironment();
????????//?根據(jù)Configuration的Environment屬性來(lái)創(chuàng)建事務(wù)工廠
????????TransactionFactory?transactionFactory?=?this.getTransactionFactoryFromEnvironment(environment);
????????//?從事務(wù)工廠中創(chuàng)建事務(wù),默認(rèn)等級(jí)為null,autoCommit=false
????????tx?=?transactionFactory.newTransaction(environment.getDataSource(),?level,?autoCommit);
????????//?創(chuàng)建執(zhí)行器
????????Executor?executor?=?this.configuration.newExecutor(tx,?execType);
????????//?根據(jù)執(zhí)行器創(chuàng)建返回對(duì)象?SqlSession
????????var8?=?new?DefaultSqlSession(this.configuration,?executor,?autoCommit);
????}?catch?(Exception?var12)?{
????????this.closeTransaction(tx);
????????throw?ExceptionFactory.wrapException("Error?opening?session.??Cause:?"?+?var12,?var12);
????}?finally?{
????????ErrorContext.instance().reset();
????}
????return?var8;
}
構(gòu)建步驟:Environment>>TransactionFactory+autoCommit+tx-level>>Transaction+ExecType>>Executor+Configuration+autoCommit>>SqlSession
其中,Environment是Configuration中的屬性。
4. 調(diào)用Executor執(zhí)行數(shù)據(jù)庫(kù)操作&&生成具體SQL指令
在拿到SqlSession對(duì)象后,我們調(diào)用它的insert方法。
public?int?insert(String?statement,?Object?parameter)?{????return?this.update(statement,?parameter);
}
它調(diào)用了自身的update(statement, parameter)方法:
public?int?update(String?statement,?Object?parameter)?{????int?var4;
????try?{
????????this.dirty?=?true;
????????MappedStatement?ms?=?this.configuration.getMappedStatement(statement);
????????//?wrapCollection(parameter)判斷?param對(duì)象是否是集合
????????var4?=?this.executor.update(ms,?this.wrapCollection(parameter));
????}?catch?(Exception?var8)?{
????????throw?ExceptionFactory.wrapException("Error?updating?database.??Cause:?"?+?var8,?var8);
????}?finally?{
????????ErrorContext.instance().reset();
????}
????return?var4;
}
mappedStatements就是我們平時(shí)說(shuō)的sql映射對(duì)象.
源碼如下:protected final Map mappedStatements;
可見(jiàn)它是一個(gè)Map集合,在我們加載xml配置的時(shí)候,mapping.xml的namespace和id信息就會(huì)存放為mappedStatements的key,對(duì)應(yīng)的,sql語(yǔ)句就是對(duì)應(yīng)的value.
然后調(diào)用BaseExecutor中的update方法:
public?int?update(MappedStatement?ms,?Object?parameter)?throws?SQLException?{????ErrorContext.instance().resource(ms.getResource()).activity("executing?an?update").object(ms.getId());
????if?(this.closed)?{
????????throw?new?ExecutorException("Executor?was?closed.");
????}?else?{
????????this.clearLocalCache();
????????//?真正做執(zhí)行操作的方法
????????return?this.doUpdate(ms,?parameter);
????}
}
doUpdate才是真正做執(zhí)行操作的方法:
public?int?doUpdate(MappedStatement?ms,?Object?parameter)?throws?SQLException?{????Statement?stmt?=?null;
????int?var6;
????try?{
????????Configuration?configuration?=?ms.getConfiguration();
????????//?創(chuàng)建StatementHandler對(duì)象,從而創(chuàng)建Statement對(duì)象
????????StatementHandler?handler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?(ResultHandler)null,?(BoundSql)null);
????????//?將sql語(yǔ)句和參數(shù)綁定并生成SQL指令
????????stmt?=?this.prepareStatement(handler,?ms.getStatementLog());
????????var6?=?handler.update(stmt);
????}?finally?{
????????this.closeStatement(stmt);
????}
????return?var6;
}
先來(lái)看看prepareStatement方法,看看mybatis是如何將sql拼接合成的:
private?Statement?prepareStatement(StatementHandler?handler,?Log?statementLog)?throws?SQLException?{????Connection?connection?=?this.getConnection(statementLog);
????//?準(zhǔn)備Statement
????Statement?stmt?=?handler.prepare(connection);
????//?設(shè)置SQL查詢(xún)中的參數(shù)值
????handler.parameterize(stmt);
????return?stmt;
}
來(lái)看看parameterize方法:
public?void?parameterize(Statement?statement)?throws?SQLException?{????this.parameterHandler.setParameters((PreparedStatement)statement);
}
這里把statement轉(zhuǎn)換程PreparedStatement對(duì)象,它比Statement更快更安全。
這都是我們?cè)贘DBC中熟用的對(duì)象,就不做介紹了,所以也能看出來(lái)Mybatis是對(duì)JDBC的封裝。
從ParameterMapping中讀取參數(shù)值和類(lèi)型,然后設(shè)置到SQL語(yǔ)句中:
public?void?setParameters(PreparedStatement?ps)?{????ErrorContext.instance().activity("setting?parameters").object(this.mappedStatement.getParameterMap().getId());
????List?parameterMappings?=?this.boundSql.getParameterMappings();if?(parameterMappings?!=?null)?{for(int?i?=?0;?i?????????????ParameterMapping?parameterMapping?=?(ParameterMapping)parameterMappings.get(i);if?(parameterMapping.getMode()?!=?ParameterMode.OUT)?{
????????????????String?propertyName?=?parameterMapping.getProperty();
????????????????Object?value;if?(this.boundSql.hasAdditionalParameter(propertyName))?{value?=?this.boundSql.getAdditionalParameter(propertyName);
????????????????}?else?if?(this.parameterObject?==?null)?{value?=?null;
????????????????}?else?if?(this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass()))?{value?=?this.parameterObject;
????????????????}?else?{
????????????????????MetaObject?metaObject?=?this.configuration.newMetaObject(this.parameterObject);value?=?metaObject.getValue(propertyName);
????????????????}
????????????????TypeHandler?typeHandler?=?parameterMapping.getTypeHandler();
????????????????JdbcType?jdbcType?=?parameterMapping.getJdbcType();if?(value?==?null?&&?jdbcType?==?null)?{
????????????????????jdbcType?=?this.configuration.getJdbcTypeForNull();
????????????????}try?{
????????????????????typeHandler.setParameter(ps,?i?+?1,?value,?jdbcType);
????????????????}?catch?(TypeException?var10)?{throw?new?TypeException("Could?not?set?parameters?for?mapping:?"?+?parameterMapping?+?".?Cause:?"?+?var10,?var10);
????????????????}?catch?(SQLException?var11)?{throw?new?TypeException("Could?not?set?parameters?for?mapping:?"?+?parameterMapping?+?".?Cause:?"?+?var11,?var11);
????????????????}
????????????}
????????}
????}
}
5. 對(duì)查詢(xún)結(jié)果二次封裝
在doUpdate方法中,解析生成完新的SQL后,然后執(zhí)行var6 = handler.update(stmt);我們來(lái)看看它的源碼。
public?int?update(Statement?statement)?throws?SQLException?{????PreparedStatement?ps?=?(PreparedStatement)statement;
?????//?執(zhí)行sql
????ps.execute();
????//?獲取返回值
????int?rows?=?ps.getUpdateCount();
????Object?parameterObject?=?this.boundSql.getParameterObject();
????KeyGenerator?keyGenerator?=?this.mappedStatement.getKeyGenerator();
????keyGenerator.processAfter(this.executor,?this.mappedStatement,?ps,?parameterObject);
????return?rows;
}
因?yàn)槲覀兪遣迦氩僮?#xff0c;返回的是一個(gè)int類(lèi)型的值,所以這里mybatis給我們直接返回int。
如果是query操作,返回的是一個(gè)ResultSet,mybatis將查詢(xún)結(jié)果包裝程ResultSetWrapper類(lèi)型,然后一步步對(duì)應(yīng)java類(lèi)型賦值等…有興趣的可以自己去看看。
6. 提交與事務(wù)
最后,來(lái)看看commit()方法的源碼。
public?void?commit()?{????this.commit(false);
}
調(diào)用其對(duì)象本身的commit()方法:
public?void?commit(boolean?force)?{????try?{
????????//?是否提交(判斷是提交還是回滾)
????????this.executor.commit(this.isCommitOrRollbackRequired(force));
????????this.dirty?=?false;
????}?catch?(Exception?var6)?{
????????throw?ExceptionFactory.wrapException("Error?committing?transaction.??Cause:?"?+?var6,?var6);
????}?finally?{
????????ErrorContext.instance().reset();
????}
}
如果dirty是false,則進(jìn)行回滾;如果是true,則正常提交。
private?boolean?isCommitOrRollbackRequired(boolean?force)?{????return?!this.autoCommit?&&?this.dirty?||?force;
}
調(diào)用CachingExecutor的commit方法:
public?void?commit(boolean?required)?throws?SQLException?{????this.delegate.commit(required);
????this.tcm.commit();
}
調(diào)用BaseExecutor的commit方法:
public?void?commit(boolean?required)?throws?SQLException?{????if?(this.closed)?{
????????throw?new?ExecutorException("Cannot?commit,?transaction?is?already?closed");
????}?else?{
????????this.clearLocalCache();
????????this.flushStatements();
????????if?(required)?{
????????????this.transaction.commit();
????????}
????}
}
最后調(diào)用JDBCTransaction的commit方法:
public?void?commit()?throws?SQLException?{????if?(this.connection?!=?null?&&?!this.connection.getAutoCommit())?{
????????if?(log.isDebugEnabled())?{
????????????log.debug("Committing?JDBC?Connection?["?+?this.connection?+?"]");
????????}
????????//?提交連接
????????this.connection.commit();
????}
}
Demo參考文檔
http://www.mybatis.org/mybatis-3/zh/getting-started.html
END
Java面試題專(zhuān)欄
【20期】你知道為什么HashMap是線程不安全的嗎?
【19期】為什么Java線程沒(méi)有Running狀態(tài)?
【18期】Java序列化與反序列化三連問(wèn):是什么?為什么要?如何做?
【17期】什么情況用ArrayList or LinkedList呢?
【16期】你能談?wù)凥ashMap怎樣解決hash沖突嗎
【15期】談?wù)勥@幾個(gè)常見(jiàn)的多線程面試題
【14期】你能說(shuō)說(shuō)進(jìn)程與線程的區(qū)別嗎
【13期】談?wù)?Redis 的過(guò)期策略
【12期】談?wù)勴?xiàng)目中單點(diǎn)登錄的實(shí)現(xiàn)原理?
【11期】分布式系統(tǒng)接口,如何避免表單的重復(fù)提交?
我知道你 “在看”
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的mybatis返回null_面试官:你分析过mybatis工作原理吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 苹果a1699是苹果几
- 下一篇: java 监听本地端口_JAVA本地监听