前面的章節主要講mybatis如何解析配置檔案,這些都是一次性的過程。從本章開始講解動态的過程,它們跟應用程式對mybatis的調用密切相關。本章先從sqlsession開始。
一、SqlSession
1、建立
正如其名,Sqlsession對應着一次資料庫會話。由于資料庫回話不是永久的,是以Sqlsession的生命周期也不應該是永久的,相反,在你每次通路資料庫時都需要建立它(當然并不是說在Sqlsession裡隻能執行一次sql,你可以執行多次,當一旦關閉了Sqlsession就需要重新建立它)。建立Sqlsession的地方隻有一個,那就是SqlsessionFactory的openSession方法:
public SqlSession openSession() {
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);
TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
connection = wrapConnection(connection);
Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
Executor executor = configuration.newExecutor(tx, execType);
return newDefaultSqlSession(configuration, executor, autoCommit);
} catch (Exceptione) {
closeConnection(connection);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看出,建立sqlsession經過了以下幾個主要步驟:
1) 從配置中擷取Environment;
2) 從Environment中取得DataSource;
3) 從Environment中取得TransactionFactory;
4) 從DataSource裡擷取資料庫連接配接對象Connection;
5) 在取得的資料庫連接配接上建立事務對象Transaction;
6) 建立Executor對象(該對象非常重要,事實上sqlsession的所有操作都是通過它完成的);
7) 建立sqlsession對象。
二、Executor
1、建立
Executor與Sqlsession的關系就像市長與書記,Sqlsession隻是個門面,真正幹事的是Executor,Sqlsession對資料庫的操作都是通過Executor來完成的。與Sqlsession一樣,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);
} elseif(ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(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對象是被各個插件插入後的代理對象(關于插件會有後續章節專門介紹,敬請期待)。
三、Mapper
Mybatis官方手冊建議通過mapper對象通路mybatis,因為使用mapper看起來更優雅,就像下面這樣:
session = sqlSessionFactory.openSession();
UserDao userDao= session.getMapper(UserDao.class);
UserDto user =new UserDto();
user.setUsername("iMbatis");
user.setPassword("iMbatis");
userDao.insertUser(user);
那麼這個mapper到底是什麼呢,它是如何建立的呢,它又是怎麼與sqlsession等關聯起來的呢?下面為你一一解答。
1、建立
表面上看mapper是在sqlsession裡建立的,但實際建立它的地方是MapperRegistry:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
if (!knownMappers.contains(type))
throw new BindingException("Type " + type + " isnot known to the MapperRegistry.");
try {
return MapperProxy.newMapperProxy(type, sqlSession);
} catch (Exceptione) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看到,mapper是一個代理對象,它實作的接口就是傳入的type,這就是為什麼mapper對象可以通過接口直接通路。同時還可以看到,建立mapper代理對象時傳入了sqlsession對象,這樣就把sqlsession也關聯起來了。我們進一步看看MapperProxy.newMapperProxy(type,sqlSession); 背後發生了什麼事情:
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
MapperProxy proxy = new MapperProxy(sqlSession);
return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy);
}
看起來沒什麼特别的,和其他代理類的建立一樣,我們重點關注一下MapperProxy的invoke方法:
2、MapperProxy 的 invoke
我們知道對被代理對象的方法的通路都會落實到代理者的invoke上來,MapperProxy的invoke如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
if (method.getDeclaringClass()== Object.class) {
return method.invoke(this, args);
}
final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
final Object result = mapperMethod.execute(args);
if (result ==null && method.getReturnType().isPrimitive()&& !method.getReturnType().equals(Void.TYPE)) {
throw new BindingException("Mapper method '" + method.getName() + "'(" + method.getDeclaringClass()
+ ") attempted toreturn null from a method with a primitive return type ("
+ method.getReturnType() + ").");
}
return result;
}
可以看到invoke把執行權轉交給了MapperMethod,我們來看看MapperMethod裡又是怎麼運作的:
public Object execute(Object[] args) {
Object result = null;
if(SqlCommandType.INSERT == type) {
Object param = getParam(args);
result = sqlSession.insert(commandName, param);
} else if(SqlCommandType.UPDATE == type) {
Object param = getParam(args);
result = sqlSession.update(commandName, param);
} else if(SqlCommandType.DELETE == type) {
Object param = getParam(args);
result = sqlSession.delete(commandName, param);
} else if(SqlCommandType.SELECT == type) {
if (returnsVoid && resultHandlerIndex != null) {
executeWithResultHandler(args);
} else if (returnsList) {
result = executeForList(args);
} else if (returnsMap) {
result = executeForMap(args);
} else {
Object param = getParam(args);
result = sqlSession.selectOne(commandName, param);
}
} else {
throw new BindingException("Unknown execution method for: " + commandName);
}
return result;
}
可以看到,MapperMethod就像是一個分發者,他根據參數和傳回值類型選擇不同的sqlsession方法來執行。這樣mapper對象與sqlsession就真正的關聯起來了。
四、Executor
前面提到過,sqlsession隻是一個門面,真正發揮作用的是executor,對sqlsession方法的通路最終都會落到executor的相應方法上去。Executor分成兩大類,一類是CacheExecutor,另一類是普通Executor。Executor的建立前面已經介紹了,下面介紹下他們的功能:
1、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);
}
2、普通 Executor
普通Executor有3類,他們都繼承于BaseExecutor,BatchExecutor專門用于執行批量sql操作,ReuseExecutor會重用statement執行sql操作,SimpleExecutor隻是簡單執行sql沒有什麼特别的。下面以SimpleExecutor為例:
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
Statementstmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);
stmt = prepareStatement(handler);
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
可以看出,Executor本質上也是個甩手掌櫃,具體的事情原來是StatementHandler來完成的。
五、StatementHandler
當Executor将指揮棒交給StatementHandler後,接下來的工作就是StatementHandler的事了。我們先看看StatementHandler是如何建立的。
1、建立
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);
statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
可以看到每次建立的StatementHandler都是RoutingStatementHandler,它隻是一個分發者,他一個屬性delegate用于指定用哪種具體的StatementHandler。可選的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三種。選用哪種在mapper配置檔案的每個statement裡指定,預設的是PreparedStatementHandler。同時還要注意到StatementHandler是可以被攔截器攔截的,和Executor一樣,被攔截器攔截後的對像是一個代理對象。由于mybatis沒有實作資料庫的實體分頁,衆多實體分頁的實作都是在這個地方使用攔截器實作的,本文作者也實作了一個分頁攔截器,在後續的章節會分享給大家,敬請期待。
2、初始化
StatementHandler建立後需要執行一些初始操作,比如statement的開啟和參數設定、對于PreparedStatement還需要執行參數的設定操作等。代碼如下:
private Statement prepareStatement(StatementHandler handler) throwsSQLException {
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, ObjectparameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
同Executor和StatementHandler一樣,ParameterHandler也是可以被攔截的。
3、參數設定
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 = new PropertyTokenizer(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最後還有個查詢結果的處理過程。
六、ResultSetHandler
1、結果處理
結果處理使用ResultSetHandler來完成,預設的ResultSetHandler是FastResultSetHandler,它在建立StatementHandler時一起建立,代碼如下:
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatementmappedStatement,
RowBoundsrowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSqlboundSql) {
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内部一條記錄一條記錄的處理,在處理每條記錄的每一列時會調用TypeHandler轉換結果,如下:
protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {
boolean foundValues = false;
for (StringcolumnName : unmappedColumnNames) {
final Stringproperty = 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;
}