陶邦仁 發布于 2015/12/22 16:20
系列目錄
- 深入淺出MyBatis系列
- 【深入淺出MyBatis系列一】MyBatis入門
- 【深入淺出MyBatis系列二】配置簡介(MyBatis源碼篇)
- 【深入淺出MyBatis系列三】Mapper映射文件配置
- 【深入淺出MyBatis系列四】強大的動態SQL
- 【深入淺出MyBatis系列五】SQL執行流程分析(源碼篇)
- 【深入淺出MyBatis系列六】插件原理
- 【深入淺出MyBatis系列七】分頁插件
- 【深入淺出MyBatis系列八】SQL自動生成插件
- 【深入淺出MyBatis系列九】改造Cache插件
- 【深入淺出MyBatis系列十】與Spring集成
- 【深入淺出MyBatis系列十一】緩存源碼分析
- 【深入淺出MyBatis系列十二】終結篇:MyBatis原理深入解析
前面的章節主要講mybatis如何解析配置文件,這些都是一次性的過程
。從本章開始講解動態的過程,它們跟應用程序對mybatis的調用密切相關。本章先從sqlsession開始。
1 SqlSessionFactory 與 SqlSession
通過前面的章節對于mybatis 的介紹及使用,大家都能體會到SqlSession的重要性了吧,沒錯,從表面上來看,咱們都是通過SqlSession去執行sql語句(注意:是從表面看,實際的待會兒就會講)。
正如其名,Sqlsession對應著一次數據庫會話。由于數據庫會話不是永久的,因此Sqlsession的生命周期也不應該是永久的,相反,在你每次訪問數據庫時都需要創建它(當然并不是說在Sqlsession里只能執行一次sql,你可以執行多次,當一旦關閉了Sqlsession就需要重新創建它)。
那么咱們就先看看是怎么獲取SqlSession的吧:
-
首先,SqlSessionFactoryBuilder去讀取mybatis的配置文件,然后build一個DefaultSqlSessionFactory
。源碼如下:
/**
* 一系列的構造方法最終都會調用本方法(配置文件為Reader時會調用本方法,還有一個InputStream方法與此對應)
* @param reader
* @param environment
* @param properties
* @return
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//通過XMLConfigBuilder解析配置文件,解析的配置相關信息都會封裝為一個Configuration對象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//這兒創建DefaultSessionFactory對象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
-
當我們獲取到SqlSessionFactory之后,就可以通過SqlSessionFactory去獲取SqlSession對象。
源碼如下:
/**
* 通常一系列openSession方法最終都會調用本方法
* @param execType
* @param level
* @param autoCommit
* @return
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通過Confuguration對象去獲取Mybatis相關配置信息, Environment對象包含了數據源和事務的配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//之前說了,從表面上來看,咱們是用sqlSession在執行sql語句, 實際呢,其實是通過excutor執行, excutor是對于Statement的封裝
final Executor executor = configuration.newExecutor(tx, execType);
//關鍵看這兒,創建了一個DefaultSqlSession對象
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();
}
}
- 通過以上步驟,咱們已經得到SqlSession對象了。接下來就是該干嘛干嘛去了(話說還能干嘛,當然是執行sql語句咯)??戳松厦?,咱們也回想一下之前寫的Demo:
SqlSessionFactory sessionFactory = null;
String resource = "mybatis-conf.xml";
try {
//SqlSessionFactoryBuilder讀取配置文件
sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
} catch (IOException e) {
e.printStackTrace();
}
//通過SqlSessionFactory獲取SqlSession
SqlSession sqlSession = sessionFactory.openSession();
- 創建Sqlsession的地方只有一個,那就是SqlsessionFactory的openSession方法:
public SqlSessionopenSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);
}
我們可以看到實際創建SqlSession的地方是openSessionFromDataSource,如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Connection connection = null;
try {
final Environment environment = configuration.getEnvironment();
final DataSource dataSource = getDataSourceFromEnvironment(environment);
// MyBatis對事務的處理相對簡單,TransactionIsolationLevel中定義了幾種隔離級別,并不支持內嵌事務這樣較復雜的場景,同時由于其是持久層的緣故,所以真正在應用開發中會委托Spring來處理事務實現真正的與開發者隔離。分析事務的實現是個入口,借此可以了解不少JDBC規范方面的事情。
TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
connection = wrapConnection(connection);
Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
Executorexecutor = configuration.newExecutor(tx, execType);
return newDefaultSqlSession(configuration, executor, autoCommit);
} catch (Exceptione) {
closeConnection(connection);
throwExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看出,創建sqlsession經過了以下幾個主要步驟:
- 從配置中獲取Environment;
- 從Environment中取得DataSource;
- 從Environment中取得TransactionFactory;
- 從DataSource里獲取數據庫連接對象Connection;
- 在取得的數據庫連接上創建事務對象Transaction;
- 創建Executor對象(該對象非常重要,事實上sqlsession的所有操作都是通過它完成的);
- 創建sqlsession對象。
還真這么一回事兒,對吧!
SqlSession咱們也拿到了,咱們可以調用SqlSession中一系列的select..., insert..., update..., delete...方法輕松自如的進行CRUD操作了
。就這樣?那咱配置的映射文件去哪兒了?別急,咱們接著往下看。
2 利器之MapperProxy
在mybatis中,通過MapperProxy動態代理咱們的dao, 也就是說, 當咱們執行自己寫的dao里面的方法的時候,其實是對應的mapperProxy在代理
。那么,咱們就看看怎么獲取MapperProxy對象吧:
-
通過SqlSession從Configuration中獲取
。源碼如下:
/**
* 什么都不做,直接去configuration中找, 哥就是這么任性
*/
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
-
SqlSession把包袱甩給了Configuration, 接下來就看看Configuration
。源碼如下:
/**
* 燙手的山芋,俺不要,你找mapperRegistry去要
* @param type
* @param sqlSession
* @return
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
-
Configuration不要這燙手的山芋,接著甩給了MapperRegistry, 那咱看看MapperRegistry
。 源碼如下:
/**
* 爛活凈讓我來做了,沒法了,下面沒人了,我不做誰來做
* @param type
* @param sqlSession
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//能偷懶的就偷懶,俺把粗活交給MapperProxyFactory去做
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//關鍵在這兒
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
-
MapperProxyFactory是個苦B的人,粗活最終交給它去做了
。咱們看看源碼:
/**
* 別人虐我千百遍,我待別人如初戀
* @param mapperProxy
* @return
*/
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//動態代理我們寫的dao接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
通過以上的動態代理,咱們就可以方便地使用dao接口啦, 就像之前咱們寫的demo那樣:
UserDao userMapper = sqlSession.getMapper(UserDao.class);
User insertUser = new User();
這下方便多了吧, 呵呵, 貌似mybatis的源碼就這么一回事兒啊。具體詳細介紹,請參見MyBatis Mapper 接口如何通過JDK動態代理來包裝SqlSession 源碼分析。別急,還沒完, 咱們還沒看具體是怎么執行sql語句的呢。
3 Excutor
Executor與Sqlsession的關系就像市長與書記,Sqlsession只是個門面,真正干事的是Executor,Sqlsession對數據庫的操作都是通過Executor來完成的
。與Sqlsession一樣,Executor也是動態創建的:
Executor創建的源代碼:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ?ExecutorType.SIMPLE : executorType;
Executor executor;
if(ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this,transaction);
} else if(ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this,transaction);
} else {
executor = newSimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
可以看出,如果不開啟cache的話,創建的Executor只是3中基礎類型之一,BatchExecutor專門用于執行批量sql操作,ReuseExecutor會重用statement執行sql操作,SimpleExecutor只是簡單執行sql沒有什么特別的
。開啟cache的話(默認是開啟的并且沒有任何理由去關閉它),就會創建CachingExecutor,它以前面創建的Executor作為唯一參數。CachingExecutor在查詢數據庫前先查找緩存,若沒找到的話調用delegate(就是構造時傳入的Executor對象)從數據庫查詢,并將查詢結果存入緩存中
。
Executor對象是可以被插件攔截的,如果定義了針對Executor類型的插件,最終生成的Executor對象是被各個插件插入后的代理對象
。
接下來,咱們才要真正去看sql的執行過程了。上面,咱們拿到了MapperProxy, 每個MapperProxy對應一個dao接口, 那么咱們在使用的時候,MapperProxy是怎么做的呢? 源碼奉上:
MapperProxy:我們知道對被代理對象的方法的訪問都會落實到代理者的invoke上來
,MapperProxy的invoke如下:
/**
* MapperProxy在執行時會觸發此方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//二話不說,主要交給MapperMethod自己去管
return mapperMethod.execute(sqlSession, args);
}
MapperMethod:就像是一個分發者,他根據參數和返回值類型選擇不同的sqlsession方法來執行。這樣mapper對象與sqlsession就真正的關聯起來了
。
/**
* 看著代碼不少,不過其實就是先判斷CRUD類型,然后根據類型去選擇到底執行sqlSession中的哪個方法,繞了一圈,又轉回sqlSession了
* @param sqlSession
* @param args
* @return
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
既然又回到SqlSession
了,前面提到過,sqlsession只是一個門面,真正發揮作用的是executor,對sqlsession方法的訪問最終都會落到executor的相應方法上去
。Executor分成兩大類,一類是CacheExecutor,另一類是普通Executor
。Executor的創建前面已經介紹了,那么咱們就看看SqlSession的CRUD方法了,為了省事,還是就選擇其中的一個方法來做分析吧。這兒,咱們選擇了selectList方法
:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//CRUD實際上是交給Excetor去處理, excutor其實也只是穿了個馬甲而已,小樣,別以為穿個馬甲我就不認識你嘞!
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
CacheExecutor:CacheExecutor有一個重要屬性delegate,它保存的是某類普通的Executor,值在構照時傳入
。執行數據庫update操作時,它直接調用delegate的update方法,執行query方法時先嘗試從cache中取值,取不到再調用delegate的查詢方法,并將查詢結果存入cache中。代碼如下:
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {
if (ms != null) {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
cache.getReadWriteLock().readLock().lock();
try {
if (ms.isUseCache() && resultHandler ==null) {
CacheKey key = createCacheKey(ms, parameterObject, rowBounds);
final List cachedList = (List)cache.getObject(key);
if (cachedList != null) {
return cachedList;
} else {
List list = delegate.query(ms,parameterObject, rowBounds, resultHandler);
tcm.putObject(cache,key, list);
return list;
}
} else {
return delegate.query(ms,parameterObject, rowBounds, resultHandler);
}
} finally {
cache.getReadWriteLock().readLock().unlock();
}
}
}
return delegate.query(ms,parameterObject, rowBounds, resultHandler);
}
普通Executor:有3類,他們都繼承于BaseExecutor,BatchExecutor專門用于執行批量sql操作,ReuseExecutor會重用statement執行sql操作,SimpleExecutor只是簡單執行sql沒有什么特別的
。下面以SimpleExecutor為例:
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);
stmt =prepareStatement(handler);
returnhandler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
然后,通過一層一層的調用,最終會來到doQuery方法
, 這兒咱們就隨便找個Excutor看看doQuery方法的實現吧,我這兒選擇了SimpleExecutor
:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//StatementHandler封裝了Statement, 讓 StatementHandler 去處理
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
Mybatis內置的ExecutorType有3種,默認的是simple,該模式下它為每個語句的執行創建一個新的預處理語句,單條提交sql
;而batch模式重復使用已經預處理的語句, 并且批量執行所有更新語句
,顯然batch性能將更優;
但batch模式也有自己的問題,比如在Insert操作時,在事務沒有提交之前,是沒有辦法獲取到自增的id
,這在某型情形下是不符合業務要求的;
通過走碼和研讀spring相關文件發現,在同一事務中batch模式和simple模式之間無法轉換,由于本項目一開始選擇了simple模式,所以碰到需要批量更新時,只能在單獨的事務中進行;
在代碼中使用batch模式可以使用以下方式:
//從spring注入原有的sqlSessionTemplate
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public void testInsertBatchByTrue() {
//新獲取一個模式為BATCH,自動提交為false的session
//如果自動提交設置為true,將無法控制提交的條數,改為最后統一提交,可能導致內存溢出
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
//通過新的session獲取mapper
fooMapper = session.getMapper(FooMapper.class);
int size = 10000;
try {
for (int i = 0; i < size; i++) {
Foo foo = new Foo();
foo.setName(String.valueOf(System.currentTimeMillis()));
fooMapper.insert(foo);
if (i % 1000 == 0 || i == size - 1) {
//手動每1000個一提交,提交后無法回滾
session.commit();
//清理緩存,防止溢出
session.clearCache();
}
}
} catch (Exception e) {
//沒有提交的數據可以回滾
session.rollback();
} finally {
session.close();
}
}
上述代碼沒有使用spring的事務,改手動控制,如果和原spring事務一起使用,將無法回滾
,必須注意,最好單獨使用;
4 StatementHandler
可以看出,Executor本質上也是個甩手掌柜,具體的事情原來是StatementHandler來完成的
。當Executor將指揮棒交給StatementHandler后,接下來的工作就是StatementHandler的事了。我們先看看StatementHandler是如何創建的:
public StatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,
ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
可以看到每次創建的StatementHandler都是RoutingStatementHandler,它只是一個分發者,他一個屬性delegate用于指定用哪種具體的StatementHandler
??蛇x的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三種
。選用哪種在mapper配置文件的每個statement里指定,默認的是PreparedStatementHandler
。同時還要注意到StatementHandler是可以被攔截器攔截的,和Executor一樣,被攔截器攔截后的對像是一個代理對象
。由于mybatis沒有實現數據庫的物理分頁,眾多物理分頁的實現都是在這個地方使用攔截器實現的,本文作者也實現了一個分頁攔截器,在后續的章節會分享給大家,敬請期待。
StatementHandler創建后需要執行一些初始操作
,比如statement的開啟和參數設置、對于PreparedStatement還需要執行參數的設置操作等。代碼如下:
private Statement prepareStatement(StatementHandler handler) throws SQLException {
Statement stmt;
Connection connection = transaction.getConnection();
stmt =handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
statement的開啟和參數設置沒什么特別的地方,handler.parameterize倒是可以看看是怎么回事。handler.parameterize通過調用ParameterHandler的setParameters完成參數的設置
,ParameterHandler隨著StatementHandler的創建而創建,默認的實現是DefaultParameterHandler
:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
同Executor和StatementHandler一樣,ParameterHandler也是可以被攔截的
。DefaultParameterHandler里設置參數的代碼如下:
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if(parameterMappings != null) {
MetaObject metaObject = parameterObject == null ? null :configuration.newMetaObject(parameterObject);
for (int i = 0; i< parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if(parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
PropertyTokenizer prop = newPropertyTokenizer(propertyName);
if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){
value = parameterObject;
} else if (boundSql.hasAdditionalParameter(propertyName)){
value = boundSql.getAdditionalParameter(propertyName);
} else if(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
&& boundSql.hasAdditionalParameter(prop.getName())){
value = boundSql.getAdditionalParameter(prop.getName());
if (value != null) {
value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
}
} else {
value = metaObject == null ? null :metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
if (typeHandler == null) {
throw new ExecutorException("Therewas no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
}
typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType());
}
}
}
}
這里面最重要的一句其實就是最后一句代碼,它的作用是用合適的TypeHandler完成參數的設置
。那么什么是合適的TypeHandler呢,它又是如何決斷出來的呢?BaseStatementHandler的構造方法里有這么一句:
this.boundSql= mappedStatement.getBoundSql(parameterObject);
它觸發了sql 的解析,在解析sql的過程中,TypeHandler也被決斷出來了,決斷的原則就是根據參數的類型和參數對應的JDBC類型決定使用哪個TypeHandler。比如:參數類型是String的話就用StringTypeHandler,參數類型是整數的話就用IntegerTypeHandler等。
參數設置完畢后,執行數據庫操作(update或query)。如果是query最后還有個查詢結果的處理過程。
接下來,咱們看看StatementHandler 的一個實現類 PreparedStatementHandler
(這也是我們最常用的,封裝的是PreparedStatement), 看看它使怎么去處理的:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 到此,原形畢露, PreparedStatement, 這個大家都已經滾瓜爛熟了吧
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 結果交給了ResultSetHandler 去處理
return resultSetHandler.<E> handleResultSets(ps);
}
結果處理使用ResultSetHandler來完成,默認的ResultSetHandler是FastResultSetHandler,它在創建StatementHandler時一起創建,代碼如下:
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement,
RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
可以看出ResultSetHandler也是可以被攔截的,可以編寫自己的攔截器改變ResultSetHandler的默認行為
。ResultSetHandler內部一條記錄一條記錄的處理,在處理每條記錄的每一列時會調用TypeHandler轉換結果,如下:
protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {
boolean foundValues = false;
for (String columnName : unmappedColumnNames) {
final String property = metaObject.findProperty(columnName);
if (property!= null) {
final ClasspropertyType =metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType);
final Object value = typeHandler.getResult(rs,columnName);
if (value != null) {
metaObject.setValue(property, value);
foundValues = true;
}
}
}
}
return foundValues;
}
從代碼里可以看到,決斷TypeHandler使用的是結果參數的屬性類型。因此我們在定義作為結果的對象的屬性時一定要考慮與數據庫字段類型的兼容性
。到此, 一次sql的執行流程就完了。 我這兒僅拋磚引玉,建議有興趣的去看看Mybatis3的源碼。
? 著作權歸作者所有