Mybatis工做原理也是面試的一大考點,必需要對其很是清晰,這樣才能懟回去。本文創建在Spring+SpringMVC+Mybatis整合的項目之上。javascript
我將其工做原理分爲六個部分:php
面試官問你你就這樣答.html
- 讀取核心配置文件
mybatis-config.xml
並返回InputStream
流對象。- 根據
InputStream
流對象解析出Configuration
對象,而後建立SqlSessionFactory
工廠對象- 根據一系列屬性從
SqlSessionFactory
工廠中建立SqlSession
- 從
SqlSession
中調用Executor
執行數據庫操做&&經過解析生成具體SQL指令- 經過
TypeHandler(數據庫與java類型轉換)
對執行結果進行二次封裝- 提交與事務處理
固然經過死記硬背確定記得不牢固,能夠結合下面的源碼理解記憶。java
先給你們看看個人實體類:mysql
/** * 圖書實體 */
public class Book {
private long bookId;// 圖書ID
private String name;// 圖書名稱
private int number;// 館藏數量
getter and setter ...
}
複製代碼
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE 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 代碼和映射定義信息)。面試
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Book">
<!-- 目的:爲dao接口方法提供sql語句配置 -->
<insert id="insert" >
insert into book (name,number) values (#{name},#{number})
</insert>
</mapper>
複製代碼
就是一個普通的mapper.xml文件。sql
從 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改編的。apache
注意:是官方給的一個不使用 XML 構建 SqlSessionFactory
的例子,那麼咱們就從這個例子中查找入口來分析。安全
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$ < len$; ++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
對象。
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;
}
複製代碼
SqlSession 徹底包含了面向數據庫執行 SQL 命令所需的全部方法。你能夠經過 SqlSession 實例來直接執行已映射的 SQL 語句。
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
複製代碼
調用自身的openSessionFromDataSource
方法:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
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
中的屬性。
在拿到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<String, MappedStatement> 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<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++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);
}
}
}
}
}
複製代碼
在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類型賦值等...有興趣的能夠本身去看看。
最後,來看看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();
}
}複製代碼
給你們推薦一個架構技術交流羣:714827309 ,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析 ,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源,相信對於已經工做 和遇到技術瓶頸的碼友,在這個羣裏會有你須要的內容。 點擊連接加入羣聊【JAVA高級架構技術交流】:jq.qq.com/?_wv=1027&a…