GitHub:https://github.com/JDawnF
一、運行源碼解析
先看一下Mybatis的Dao實現類例子,如下:
A、 輸入流的關閉
在輸入流對象使用完畢後,不用手工進行流的關閉。因爲在輸入流被使用完畢後,SqlSessionFactoryBuilder 對象的 build()方法會自動將輸入流關閉。
//SqlSessionFactoryBuilder.java
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try { // 關閉輸入流
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
B、 SqlSession 的創建
SqlSession 接口對象用於執行持久化操作。一個 SqlSession 對應着一次數據庫會話,一 次會話以 SqlSession 對象的創建開始,以 SqlSession 對象的關閉結束。
SqlSession 接口對象是線程不安全的,所以每次數據庫會話結束前,需要馬上調用其 close()方法,將其關閉。再次需要會話,再次創建。而在關閉時會判斷當前的 SqlSession 是否被提交:若沒有被提交,則會執行回滾後關閉;若已被提交,則直接將 SqlSession 關閉。 所以,SqlSession 無需手工回滾。
主要是一些增刪改查的方法。
SqlSession 對象的創建,需要使用 SqlSessionFactory 接口對象的 openSession()方法。 SqlSessionFactory 接口對象是一個重量級對象(系統開銷大的對象),是線程安全的,所以一個應用只需要一個該對象即可。創建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。
-
openSession(true):創建一個有自動提交功能的 SqlSession
-
openSession(false):創建一個非自動提交功能的 SqlSession,需手動提交
-
openSession():同 openSession(false) ,即無參的openSession方法默認false是autoCommit的值
SqlSessionFactory 接口的實現類爲 DefaultSqlSessionFactory。
// SqlSessionFactory.java
public interface SqlSessionFactory {
SqlSession openSession();
// 多個openSession方法
Configuration getConfiguration();
}
// DefaultSqlSessionFactory.java
public SqlSession openSession() {
// false是autoCommit的值,表示關閉事務的自動提交功能
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//autoCommit表示是否自動提交事務
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//讀取Mybatis的主配置文件
final Environment environment = configuration.getEnvironment();
// 獲取事務管理器transcationManager,比如配置文件中的JDBC
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 創建執行器,傳入的是事務和執行器類型(SIMPLE, REUSE, BATCH)
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//DefaultSqlSession.java
// 所謂創建SqlSession就是對一個dirty這個變量進行初始化,即是否爲髒數據的意思
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
// 對成員變量進行初始化
this.configuration = configuration;
this.executor = executor;
this.dirty = false; // 這個變量爲false表示現在DB中的數據還未被修改
this.autoCommit = autoCommit;
}
從以上源碼可以看到,無參的 openSession()方法,將事務的自動提交直接賦值爲 false。而所謂創建 SqlSession,就是加載了主配置文件,創建了一個執行器對象(將來用於執行映射文件中的 SQL 語句),初始化了一個 DB 數據被修改的標誌變量 dirty,關閉了事務的自動提交功能。
C、 增刪改的執行
對於 SqlSession 的 insert()、delete()、update()方法,其底層均是調用執行了 update()方法,只要對數據進行了增刪改,那麼dirty就會變爲true,表示數據被修改了。
// DefaultSqlSession.java
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
public int update(String statement, Object parameter) {
try {
dirty = true; //這裏要開始修改數據了,所以要將dirty改爲true,表示此時是髒數據
// statement是獲取映射文件中制定的sql語句,即mapper映射文件中的sql id
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
從以上源碼可知,無論執行增、刪還是改,均是對數據進行修改,均將 dirty 變量設置爲了 true,且在獲取到映射文件中指定 id 的 SQL 語句後,由執行器 executor 執行。
D、 SqlSession 的提交 commit()
// DefaultSqlSession.java
public void commit() {
commit(false);
}
public void commit(boolean force) {
try {
// 執行提交
executor.commit(isCommitOrRollbackRequired(force));
dirty = false; // 提交之後把dirty設置爲false,表示數據未修改
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 提交還是回滾
/**當autoCommit爲true時,返回false;
當autoCommit爲false,dirty爲true時,返回true;
當autoCommit爲false,dirty爲false時,如果force爲true則返回true,爲false則返回false
在這裏根據上面方法傳過來的參數值,autoCommit爲false,所以!false==true,dirty爲true,force爲 false,所以isCommitOrRollbackRequired返回true。
*/
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
// CachingExecutor.java
// required根據上面的值是爲true
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
//BaseExecutor.java
public void commit(boolean required) throws SQLException {
if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
clearLocalCache();
flushStatements();
if (required) { // 根據上面返回的結果,required爲true,提交事務
transaction.commit();
}
}
由以上代碼可知,執行 SqlSession 的無參 commit()方法,最終會將事務進行提交。
E、 SqlSession 的關閉
//DefaultSqlSession.java
public void close() {
try {
// 如果執行了commit方法,那麼這裏返回的是false,即close方法中傳入的是false
executor.close(isCommitOrRollbackRequired(false));
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
// 這裏的force爲false,autoCommit在最開始的openSession方法中傳入的是爲false,dirty在commit之後,而在commit方法中,將dirty設置爲false了,所以這裏dirty是false,所以這裏整體返回的是false
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
//BaseExecutor.java
public void close(boolean forceRollback) {
try {
try {
// 根據上面傳入的值,forceRollback爲false
rollback(forceRollback);
} finally { // 最後要確認事務關閉,如果前面執行了增刪改查方法,說明提交了事務,所以事務不爲空
if (transaction != null) transaction.close();
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally { //釋放各種資源,並將關閉標誌closed重置爲true
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
// 根據上面傳進來的值,required爲false
public void rollback(boolean required) throws SQLException {
if (!closed) { // 此時還未關閉,所以closed爲false,這裏!closed爲true
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) { // required爲false,不會回滾事務
transaction.rollback();
}
}
}
}
從以上代碼分析可知,在 SqlSession 進行關閉時,如果執行了commit,那麼不會回滾事務;如果沒有執行commit方法,那麼就會回滾事務,那麼數據不會插入到數據庫。所以,對於MyBatis 程序,無需通過顯式地對 SqlSession 進行回滾,達到事務回滾的目的。
二、延遲加載
MyBatis 中的延遲加載,也稱爲懶加載,是指在進行關聯查詢時,按照設置延遲規則推 遲對關聯對象的 select 查詢。延遲加載可以有效的減少數據庫壓力。 需要注意的是,MyBatis 的延遲加載只是對關聯對象的查詢有遲延設置,對於主加載對象都是直接執行查詢語句的。
Mybatis 僅支持 association 關聯對象和 collection 關聯集合對象的延遲加載。其中,association 指的就是一對一,collection 指的就是一對多查詢。
它的原理是,使用 CGLIB 或 Javassist( 默認 ) 創建目標對象的代理對象。當調用代理對象的延遲加載屬性的 getting 方法時,進入攔截器方法。比如調用
a.getB().getName()
方法,進入攔截器的invoke(...)
方法,發現a.getB()
需要延遲加載時,那麼就會單獨發送事先保存好的查詢關聯 B 對象的 SQL ,把 B 查詢上來,然後調用a.setB(b)
方法,於是a
對象b
屬性就有值了,接着完成a.getB().getName()
方法的調用。這就是延遲加載的基本原理。當然了,不光是 Mybatis,幾乎所有的包括 Hibernate 在內,支持延遲加載的原理都是一樣的。
1.關聯對象加載時機
MyBatis 根據對關聯對象查詢的 select 語句的執行時機,分爲三種類型:直接加載、侵 入式延遲加載與深度延遲加載。
-
直接加載:執行完對主加載對象的 select 語句,馬上執行對關聯對象的 select 查詢。
-
侵入式延遲:執行對主加載對象的查詢時,不會執行對關聯對象的查詢。但當要訪問主加載對象的詳情時,就會馬上執行關聯對象的 select 查詢。即對關聯對象的查詢執行, 侵入到了主加載對象的詳情訪問中。也可以這樣理解:將關聯對象的詳情侵入到了主加 載對象的詳情中,即將關聯對象的詳情作爲主加載對象的詳情的一部分出現了。
-
深度延遲:執行對主加載對象的查詢時,不會執行對關聯對象的查詢。訪問主加載對象 的詳情時也不會執行關聯對象的 select 查詢。只有當真正訪問關聯對象的詳情時,纔會 執行對關聯對象的 select 查詢。
需要注意的是,延遲加載的應用要求,關聯對象的查詢與主加載對象的查詢必須是分別進行的 select 語句,不能是使用多表連接所進行的 select 查詢。因爲,多表連接查詢,其實 質是對一張表的查詢,對由多個表連接後形成的一張表的查詢。會一次性將多張表的所有信 息查詢出來。
MyBatis 中對於延遲加載設置,可以應用到一對一、一對多、多對一、多對多的所有關 聯關係查詢中。
2.直接加載
修改主配置文件:在主配置文件的<properties/>與<typeAliases/>標籤之間,添加<settings/>標籤,用於完 成全局參數設置。
延遲加載的相關參數名稱及取值:
全局屬性 lazyLoadingEnabled 的值只要設置爲 false,那麼,對於關聯對象的查詢,將採 用直接加載。即在查詢過主加載對象後,會馬上查詢關聯對象。
lazyLoadingEnabled 的默認值爲 false,即直接加載。
3.深度延遲加載
修改主配置文件的<settings/>,將延遲加載開關 lazyLoadingEnabled 開啓(置爲 true), 將侵入式延遲加載開關 aggressiveLazyLoading 關閉(置爲 false)。
4.侵入式延遲加載
修改主配置文件的<settings/>,將延遲加載開關 lazyLoadingEnabled 開啓(置爲 true), 將侵入式延遲加載開關 aggressiveLazyLoading 也開啓(置爲 true,默認爲 true)。
該延遲策略使關聯對象的數據侵入到了主加載對象的數據中,所以稱爲 侵入式延遲加載。 需要注意的是,該延遲策略也是一種延遲加載,需要在延遲加載開關 lazyLoadingEnabled 開啓時纔會起作用。若 lazyLoadingEnabled 爲 false,則 aggressiveLazyLoading 無論取何值, 均不起作用。
5.延遲加載策略總結
參照:動力節點