MyBatis-Spring 執行SQL語句的流程
1. 從SqlSessionDaoSupport開始
通常我們使用MyBatis會讓自己的DAO繼承SqlSessionDaoSupport,那麼SqlSessionDaoSupport是如何運作的呢,下面是SqlSessionDaoSupport的原始碼
/* * Copyright 2010 The myBatis Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.*/ package org.mybatis.spring.support; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.support.DaoSupport; importorg.springframework.util.Assert; /** * Convenient super class for MyBatis SqlSession data access objects. * It gives you access to the template which can then be used to execute SQL methods. * <p> * This class needs a SqlSessionTemplate or a SqlSessionFactory. * If both are set the SqlSessionFactory will be ignored. * * @see #setSqlSessionFactory * @see #setSqlSessionTemplate * @see SqlSessionTemplate * @version $Id: SqlSessionDaoSupport.java 3266 2010-11-22 06:56:51Z simone.tripodi $ */ public abstract class SqlSessionDaoSupport extends DaoSupport { // 這個SqlSession就是我們平時用來執行SQL和事務的一次會話 private SqlSession sqlSession; private boolean externalSqlSession; // 可以看到以下兩個Autowired的set方法,實際上他們的功能都是設定sqlSession的例項 // 區別在於一個是通過傳入sqlSessionFactory然後包裝成SqlSessionTemplate // 另一個直接傳入SqlSessionTemplate賦值給sqlSession @Autowired(required = false) public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } @Autowired(required = false) public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; } /** * Users should use this method to get a SqlSession to call its statement methods * This is SqlSession is managed by spring. Users should not commit/rollback/close it * because it will be automatically done. * * @return Spring managed thread safe SqlSession */ public final SqlSession getSqlSession() { return this.sqlSession; } /** * {@inheritDoc} */ protected void checkDaoConfig() { Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }
2.SqlSessionDaoSupport中的SqlSession產生
先提一提既然我們是用dao去繼承這個SqlSessionDaoSupport,觀察我們的dao的配置
<bean id="userDao" class="dao.UserDao"> <!--<property name="sqlSessionFactory" ref="sqlSessionFactory" />--> <property name="sqlSessionTemplate" ref="sqlSession" /> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" > <!-- 第一個引數是 sqlSessionFactory --> <constructor-arg index="0" ref="sqlSessionFactory"/> <!-- 第二個引數是 ExecutorType --> <constructor-arg index="1" ref="BATCH"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 指定資料來源 --> <property name="dataSource" ref="dataSource" /> <!-- 指定MyBatis配置檔案 --> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 匯入Mapper --> <property name="mapperLocations" value="classpath*:mappers/*.xml" /> </bean> <!-- datasource --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean>
觀察我們的dao的配置可以發現我們可以配置sqlSessionFactory和sqlSessionTemplate,實際上我們配置sqlSessionTemplate的話就是能多配置一個ExecutorType引數(這個引數在MyBatis的Settings引數裡也可以找到,叫做defaultExecutorType,引數配置詳細介紹見 http://mybatis.github.io/mybatis-3/configuration.html),這個引數的選項有三個:
SIMPLE: 普通SQL執行器,不會使用預解析和批量處理
REUSE: 重用prepared statement
BATCH: 不但重用prepared statement,而且能執行批量處理,如
public void insertUsers(User[] users) { for (User user : users) { sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user); } }
3. 分析sqlSessionTemplate的構造
檢視sqlSessionTemplate的原始碼,關鍵部分如下
/** * Constructs a Spring managed {@link SqlSession} with the given * {@link SqlSessionFactory} and {@link ExecutorType}. * A custom {@link SQLExceptionTranslator} can be provided as an * argument so any {@link PersistenceException} thrown by MyBatis * can be custom translated to a {@link RuntimeException} * The {@link SQLExceptionTranslator} can also be null and thus no * exception translation will be done and MyBatis exceptions will be * thrown * * @param sqlSessionFactory * @param executorType * @param exceptionTranslator */ public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); Assert.notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
它的功能如下
1) 設定sqlSessionFactory,檢視上面的xml的配置,就知道sqlSessionFactory這個東西其實就是用來指定datasource,讀取mybatis配置(environment,settings種種),還有讀取mybatis的各個sql語句的mapper的東西
2) 設定executor type,上面已經介紹過了
3) 設定exception translator,這個東西是用來將jdbc的異常轉換成spring的sql異常的東西(因為jdbc的異常很單一,無法詳細的表達出錯時錯誤到底是什麼,所以spring自己寫了一套更好理解的異常,這是題外話),這個東西保持預設就可以了,所以我們在配置sqlSessionTemplate的bean的時候並沒有配置這個引數,如果沒有配置,則構造方法會使用一個預設的(這是一個過載方法,另外還有一個構造方法是不需要設定這個引數的,那個構造方法呼叫了這個構造方法,其中execeptionTranslator就是傳了一個預設的進來)
4) 對SqlSessionInteceptor()設定了一個代理,從這個動態代理的建構函式引數我們就能看出來這個東西是一個SqlSession
其中,這個SqlSessionInterceptor是SqlSessionTemplate一個內部類,他返回了一個SqlSession關鍵程式碼如下
final SqlSession sqlSession = SqlSessionUtils.getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType);
很簡單,其實就是用sqlSessionFactory和executorType生成了一個sqlSession
接下來進入一段七彎八拐的呼叫,過程如下
SqlSessionUtils.getSqlSession() -> SessionFactory.openSession(executorType, conn) -> DefaultSessionFactory.openSession(ExecutorType execType) ->Configuration.newExecutor(tx, execType, autoCommit) -> return new DefaultSqlSession(configuration, executor)
其中的關鍵部分是newExecutor(),這玩意就是生成SQL語句執行器的地方,生成的程式碼如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 這裡就是根據ExecutorType建立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); } // 這句就是判斷setting裡的cacheEnabled引數的地方 // 所以設定了cacheEnabled引數後就會被包裝成快取Executor if (cacheEnabled) { executor = new CachingExecutor(executor, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
主要做的事情就是 1) 根據executorType生成合適的executor 2) 更具cacheEnabled引數包裝executor
至此, SqlSessionTemplate中的sqlSessionProxy的executor終於生成出來,以後我們使用dao中的session來執行sql相關的操作用的就都是這個SqlSessionTemplate中的sqlSessionProxy
最後,畫個圖總結一下
也就是說,我們其實使用的是SqlSessionTemplate在做各種資料庫操作,這個東西讀取了我們的datasource和mybatisconfig,用它的Executor去執行我們Mapper裡的sql語句來獲取查詢結果