天天看點

MyBatis - SqlSession

前面的章節主要講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;
}      

繼續閱讀