mybatis返回null_面试官:你分析过mybatis工作原理吗?
Mybatis工作原理也是面試的一大考點,必須要對其非常清晰,這樣才能懟回去。本文建立在Spring+SpringMVC+Mybatis整合的項目之上。
我將其工作原理分為六個部分:
讀取核心配置文件并返回InputStream流對象。
根據InputStream流對象解析出Configuration對象,然后創建SqlSessionFactory工廠對象
根據一系列屬性從SqlSessionFactory工廠中創建SqlSession
從SqlSession中調用Executor執行數據庫操作&&生成具體SQL指令
對執行結果進行二次封裝
提交與事務
先給大家看看我的實體類:
/**?*?圖書實體
?*/
public?class?Book?{
????private?long?bookId;//?圖書ID
????private?String?name;//?圖書名稱
????private?int?number;//?館藏數量
????????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>
當然,還有很多可以在XML 文件中進行配置,上面的示例指出的則是最關鍵的部分。要注意 XML 頭部的聲明,用來驗證 XML 文檔正確性。environment 元素體中包含了事務管理和連接池的配置。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>
就是一個普通的mapper.xml文件。
1.3 Main方法
從 XML 文件中構建 SqlSessionFactory 的實例非常簡單,建議使用類路徑下的資源文件進行配置。但是也可以使用任意的輸入流(InputStream)實例,包括字符串形式的文件路徑或者 file:// 的 URL 形式的文件路徑來配置。
MyBatis 包含一個名叫 Resources 的工具類,它包含一些實用方法,可使從 classpath 或其他位置加載資源文件更加容易。
public?class?Main?{????public?static?void?main(String[]?args)?throws?IOException?{
????????//?創建一個book對象
????????Book?book?=?new?Book();
????????book.setBookId(1006);
????????book.setName("Easy?Coding");
????????book.setNumber(110);
????????//?加載配置文件?并構建SqlSessionFactory對象
????????String?resource?=?"mybatis-config.xml";
????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
????????SqlSessionFactory?factory?=?new?SqlSessionFactoryBuilder().build(inputStream);
????????//?從SqlSessionFactory對象中獲取?SqlSession對象
????????SqlSession?sqlSession?=?factory.openSession();
????????//?執行操作
????????sqlSession.insert("insert",?book);
????????//?提交操作
????????sqlSession.commit();
????????//?關閉SqlSession
????????sqlSession.close();
????}
}
這個代碼是根據Mybatis官方提供的一個不使用 XML 構建 SqlSessionFactory的一個Demo改編的。
注意:是官方給的一個不使用 XML 構建 SqlSessionFactory的例子,那么我們就從這個例子中查找入口來分析。
2. 根據配置文件生成SqlSessionFactory工廠對象
2.1 Resources.getResourceAsStream(resource);源碼分析
Resources是mybatis提供的一個加載資源文件的工具類。
我們只看getResourceAsStream方法:
public?static?InputStream?getResourceAsStream(String?resource)?throws?IOException?{????return?getResourceAsStream((ClassLoader)null,?resource);
}
getResourceAsStream調用下面的方法:
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對象,然后交給ClassLoader(lang包下的)來加載:
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;
????????????}
????????}
????}
值的注意的是,它返回了一個InputStream對象。
2.2 new SqlSessionFactoryBuilder().build(inputStream);源碼分析
public?SqlSessionFactoryBuilder()?{}
所以new SqlSessionFactoryBuilder()只是創建一個對象實例,而沒有對象返回(建造者模式),對象的返回交給build()方法。
public?SqlSessionFactory?build(InputStream?inputStream)?{????return?this.build((InputStream)inputStream,?(String)null,?(Properties)null);
}
這里要傳入一個inputStream對象,就是將我們上一步獲取到的InputStream對象傳入。
public?SqlSessionFactory?build(InputStream?inputStream,?String?environment,?Properties?properties)?{????SqlSessionFactory?var5;
????try?{
????????//?進行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;
}
如何解析的就大概說下,通過Document對象來解析,然后返回InputStream對象,然后交給XMLConfigBuilder構造成org.apache.ibatis.session.Configuration對象,然后交給build()方法構造程SqlSessionFactory:
public?SqlSessionFactory?build(Configuration?config)?{????return?new?DefaultSqlSessionFactory(config);
}
public?DefaultSqlSessionFactory(Configuration?configuration)?{
????this.configuration?=?configuration;
}
3. 創建SqlSession
SqlSession 完全包含了面向數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句。
public?SqlSession?openSession()?{????return?this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(),?(TransactionIsolationLevel)null,?false);
}
調用自身的openSessionFromDataSource方法:
getDefaultExecutorType()默認是SIMPLE。
注意TX等級是 Null, autoCommit是false。
????Transaction?tx?=?null;
????DefaultSqlSession?var8;
????try?{
????????Environment?environment?=?this.configuration.getEnvironment();
????????//?根據Configuration的Environment屬性來創建事務工廠
????????TransactionFactory?transactionFactory?=?this.getTransactionFactoryFromEnvironment(environment);
????????//?從事務工廠中創建事務,默認等級為null,autoCommit=false
????????tx?=?transactionFactory.newTransaction(environment.getDataSource(),?level,?autoCommit);
????????//?創建執行器
????????Executor?executor?=?this.configuration.newExecutor(tx,?execType);
????????//?根據執行器創建返回對象?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;
}
構建步驟:Environment>>TransactionFactory+autoCommit+tx-level>>Transaction+ExecType>>Executor+Configuration+autoCommit>>SqlSession
其中,Environment是Configuration中的屬性。
4. 調用Executor執行數據庫操作&&生成具體SQL指令
在拿到SqlSession對象后,我們調用它的insert方法。
public?int?insert(String?statement,?Object?parameter)?{????return?this.update(statement,?parameter);
}
它調用了自身的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對象是否是集合
????????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就是我們平時說的sql映射對象.
源碼如下:protected final Map mappedStatements;
可見它是一個Map集合,在我們加載xml配置的時候,mapping.xml的namespace和id信息就會存放為mappedStatements的key,對應的,sql語句就是對應的value.
然后調用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();
????????//?真正做執行操作的方法
????????return?this.doUpdate(ms,?parameter);
????}
}
doUpdate才是真正做執行操作的方法:
public?int?doUpdate(MappedStatement?ms,?Object?parameter)?throws?SQLException?{????Statement?stmt?=?null;
????int?var6;
????try?{
????????Configuration?configuration?=?ms.getConfiguration();
????????//?創建StatementHandler對象,從而創建Statement對象
????????StatementHandler?handler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?(ResultHandler)null,?(BoundSql)null);
????????//?將sql語句和參數綁定并生成SQL指令
????????stmt?=?this.prepareStatement(handler,?ms.getStatementLog());
????????var6?=?handler.update(stmt);
????}?finally?{
????????this.closeStatement(stmt);
????}
????return?var6;
}
先來看看prepareStatement方法,看看mybatis是如何將sql拼接合成的:
private?Statement?prepareStatement(StatementHandler?handler,?Log?statementLog)?throws?SQLException?{????Connection?connection?=?this.getConnection(statementLog);
????//?準備Statement
????Statement?stmt?=?handler.prepare(connection);
????//?設置SQL查詢中的參數值
????handler.parameterize(stmt);
????return?stmt;
}
來看看parameterize方法:
public?void?parameterize(Statement?statement)?throws?SQLException?{????this.parameterHandler.setParameters((PreparedStatement)statement);
}
這里把statement轉換程PreparedStatement對象,它比Statement更快更安全。
這都是我們在JDBC中熟用的對象,就不做介紹了,所以也能看出來Mybatis是對JDBC的封裝。
從ParameterMapping中讀取參數值和類型,然后設置到SQL語句中:
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. 對查詢結果二次封裝
在doUpdate方法中,解析生成完新的SQL后,然后執行var6 = handler.update(stmt);我們來看看它的源碼。
public?int?update(Statement?statement)?throws?SQLException?{????PreparedStatement?ps?=?(PreparedStatement)statement;
?????//?執行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;
}
因為我們是插入操作,返回的是一個int類型的值,所以這里mybatis給我們直接返回int。
如果是query操作,返回的是一個ResultSet,mybatis將查詢結果包裝程ResultSetWrapper類型,然后一步步對應java類型賦值等…有興趣的可以自己去看看。
6. 提交與事務
最后,來看看commit()方法的源碼。
public?void?commit()?{????this.commit(false);
}
調用其對象本身的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,則進行回滾;如果是true,則正常提交。
private?boolean?isCommitOrRollbackRequired(boolean?force)?{????return?!this.autoCommit?&&?this.dirty?||?force;
}
調用CachingExecutor的commit方法:
public?void?commit(boolean?required)?throws?SQLException?{????this.delegate.commit(required);
????this.tcm.commit();
}
調用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();
????????}
????}
}
最后調用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面試題專欄
【20期】你知道為什么HashMap是線程不安全的嗎?
【19期】為什么Java線程沒有Running狀態?
【18期】Java序列化與反序列化三連問:是什么?為什么要?如何做?
【17期】什么情況用ArrayList or LinkedList呢?
【16期】你能談談HashMap怎樣解決hash沖突嗎
【15期】談談這幾個常見的多線程面試題
【14期】你能說說進程與線程的區別嗎
【13期】談談 Redis 的過期策略
【12期】談談項目中單點登錄的實現原理?
【11期】分布式系統接口,如何避免表單的重復提交?
我知道你 “在看”
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的mybatis返回null_面试官:你分析过mybatis工作原理吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果a1699是苹果几
- 下一篇: java 监听本地端口_JAVA本地监听