Mybatis源碼分析(五)探究SQL語句的執(zhí)行過程

一、重溫JDBC

Java Database Connectivity,簡稱JDBC。是Java語言中用來規(guī)范客戶端程序如何來訪問數(shù)據(jù)庫的應(yīng)用程序接口,提供了諸如查詢和更新數(shù)據(jù)庫中數(shù)據(jù)的方法。
隨著Java ORM框架的發(fā)展,已經(jīng)很少有機會再在生產(chǎn)系統(tǒng)中寫JDBC的代碼來訪問數(shù)據(jù)庫了,但是基本流程我們還是要熟悉。下面以一個簡單的查詢?yōu)槔瑴毓室幌翵DBC。

public static void main(String[] args) throws Exception {
    Connection conn = getConnection();  
    String sql = "select * from user where 1=1 and id = ?";
    PreparedStatement stmt = conn.prepareStatement(sql);
    stmt.setString(1, "501440165655347200");
    ResultSet rs = stmt.executeQuery();
    while(rs.next()){
        String username = rs.getString("username");
        System.out.print("姓名: " + username);
    }
}

從上面的代碼來看,一次簡單的數(shù)據(jù)庫查詢操作,可以分為幾個步驟。

  • 創(chuàng)建Connection連接

  • 傳入?yún)?shù)化查詢SQL語句構(gòu)建預(yù)編譯對象PreparedStatement

  • 設(shè)置參數(shù)

  • 執(zhí)行SQL

  • 從結(jié)果集中獲取數(shù)據(jù)

那么,咱們的主角Mybatis是怎樣完成這一過程的呢?不著急,咱們一個一個來看。

二、sqlSession

在上一章節(jié)的內(nèi)容中,我們已經(jīng)看到了在Service層通過@Autowired注入的userMapper是個代理類,在執(zhí)行方法的時候?qū)嶋H上調(diào)用的是代理類的invoke通知方法。

public class MapperProxy<T> implements InvocationHandler{
    public Object invoke(Object proxy, Method method, Object[] args)

        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
}

1 、創(chuàng)建MapperMethod對象

MapperMethod對象里面就兩個屬性,SqlCommand和MethodSignature。

SqlCommand包含了執(zhí)行方法的名稱和方法的類型,比如UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
MethodSignature可以簡單理解為方法的簽名信息。里面包含:返回值類型、是否void、是否為集合類型、是否為Cursor等,主要還獲取到了方法參數(shù)上的@Param注解的名稱,方便下一步獲取參數(shù)值。
比如,如果方法上加了@Param的參數(shù):
User getUserById(@Param(value="id")String id,@Param(value="password")String password);,參數(shù)會被解析成{0=id, 1=password}

2、執(zhí)行

判斷方法的SQL類型和返回值類型 ,調(diào)用相應(yīng)的方法。以方法User getUserById(String id,String password)為例,會調(diào)用到selectOne()方法。

public class MapperMethod {
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
        case INSERT: {}
        case UPDATE: {}
        case DELETE: {}
        case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
            //無返回值
        } else if (method.returnsMany()) {
            //返回集合類型
            result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
            //返回Map類型
            result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
            //返回Cursor
            result = executeForCursor(sqlSession, args);
        } else {
            //將參數(shù)args轉(zhuǎn)換為SQL命令的參數(shù)
            //默認會添加一個《param+參數(shù)索引》的參數(shù)名
            //{password=123456, id=501441819331002368, param1=501441819331002368, param2=123456}
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
        }
        ...
        return result;
    }
}

可以看到,sqlSession.selectOne就可以獲取到數(shù)據(jù)庫中的值并完成轉(zhuǎn)換工作。這里的sqlSession就是SqlSessionTemplate實例的對象,所以它會調(diào)用到

public class SqlSessionTemplate{
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.<T> selectOne(statement, parameter);
    }
}

sqlSessionProxy也是個代理對象。關(guān)于它的創(chuàng)建咱們上節(jié)課也很認真的分析了,總之它實際會調(diào)用到SqlSessionInterceptor.invoke()

3、創(chuàng)建sqlSession對象

sqlSession我們熟悉呀,它作為MyBatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話,完成必要數(shù)據(jù)庫增刪改查功能。關(guān)于它的創(chuàng)建、執(zhí)行、提交和資源清理都是在SqlSessionInterceptor的通知方法中完成的。

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        //創(chuàng)建SqlSession對象
        SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
        try {
            //調(diào)用sqlSession實際方法
            Object result = method.invoke(sqlSession, args);
            return result;
        } catch (Throwable t) {
            ....
        } finally {
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

上面的重點就是創(chuàng)建了SqlSession并執(zhí)行它的方法,它是一個DefaultSqlSession實例的對象,里面主要有一個通過configuration創(chuàng)建的執(zhí)行器,在這里它是SimpleExecutor。

那么,invoke方法實際調(diào)用的就是DefaultSqlSession.selectOne()

三、獲取BoundSql對象

DefaultSqlSession中的selectOne()方法最終也會調(diào)用到selectList()方法。它先從數(shù)據(jù)大管家configuration中根據(jù)請求方法的全名稱拿到對應(yīng)的MappedStatement對象,然后調(diào)用執(zhí)行器的查詢方法。

1、獲取MappedStatement對象

//statement是調(diào)用方法的全名稱,parameter為參數(shù)的Map
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    //在mapper.xml中每一個SQL節(jié)點都會封裝為MappedStatement對象
    //在configuration中就可以通過請求方法的全名稱獲取對應(yīng)的MappedStatement對象
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}

其中有個方法wrapCollection(parameter)我們可以了解下,如果參數(shù)為集合類型或者數(shù)組類型,它會將參數(shù)名稱設(shè)置為相應(yīng)類型的名稱。

private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
        StrictMap<Object> map = new StrictMap<Object>();
        map.put("collection", object);
        if (object instanceof List) {
        map.put("list", object);
        }
        return map;
    } else if (object != null && object.getClass().isArray()) {
        StrictMap<Object> map = new StrictMap<Object>();
        map.put("array", object);
        return map;
    }
    return object;
}

2、獲取BoundSql對象

在configuration這個大管家對象中,保存著mapper.xml里面所有的SQL節(jié)點。每一個節(jié)點對應(yīng)一個MappedStatement對象,而動態(tài)生成的各種sqlNode保存在SqlSource對象,SqlSource對象有一個方法就是getBoundSql()。
我們先來看一下BoundSql類哪有哪些屬性。

public class BoundSql { 
    //動態(tài)生成的SQL,解析完畢帶有占位性的SQL
    private final String sql;
    //每個參數(shù)的信息。比如參數(shù)名稱、輸入/輸出類型、對應(yīng)的JDBC類型等
    private final List<ParameterMapping> parameterMappings;
    //參數(shù)
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;
}

看到這幾個屬性,也就解釋了BoundSql 的含義。即表示動態(tài)生成的SQL語句和相應(yīng)的參數(shù)信息。

不知大家是否還有印象,不同類型的SQL會生成不同類型的SqlSource對象。比如靜態(tài)SQL會生成StaticSqlSource對象,動態(tài)SQL會生成DynamicSqlSource對象。

  • 靜態(tài)SQL

靜態(tài)SQL比較簡單,直接就創(chuàng)建了BoundSql對象并返回。

public class StaticSqlSource implements SqlSource {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
  • 動態(tài)SQL

動態(tài)SQL要根據(jù)不同的sqlNode節(jié)點,調(diào)用對應(yīng)的apply方法,有的還要通過Ognl表達式來判斷是否需要添加當前節(jié)點,比如IfSqlNode。

public class DynamicSqlSource implements SqlSource {
    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        //rootSqlNode為sqlNode節(jié)點的最外層封裝,即MixedSqlNode。
        //解析完所有的sqlNode,將sql內(nèi)容設(shè)置到context
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        //設(shè)置參數(shù)信息 將SQL#{}替換為占位符
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        //創(chuàng)建BoundSql對象
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
            boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        return boundSql;
    }
}

rootSqlNode.apply(context)是一個迭代調(diào)用的過程。最后生成的內(nèi)容保存在DynamicContext對象,比如select * from user WHERE uid=#{uid}

然后調(diào)用SqlSourceBuilder.parse()方法。它主要做了兩件事:

1、將SQL語句中的#{}替換為占位符
2、將#{}里面的字段封裝成ParameterMapping對象,添加到parameterMappings。

ParameterMapping對象保存的就是參數(shù)的類型信息,如果沒有配置則為null。
ParameterMapping{property='uid', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}

最后返回的BoundSql對象就包含一個帶有占位符的SQL和參數(shù)的具體信息。

四、執(zhí)行SQL

創(chuàng)建完BoundSql對象,調(diào)用query方法,來到CachingExecutor.query()。這個方法的前面是二級緩存的判斷,如果開啟了二級緩存且緩存中有數(shù)據(jù),就返回。

1、緩存

public class CachingExecutor implements Executor {
    public <E> List<E> query(MappedStatement ms, Object parameterObject, 
        RowBounds rowBounds, ResultHandler resultHandler, 
        CacheKey key, BoundSql boundSql)throws SQLException {
        //二級緩存的應(yīng)用
        //如果配置</cache>則走入這個流程
        Cache cache = ms.getCache();
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                //從緩存中獲取數(shù)據(jù)
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

接著看query方法,創(chuàng)建PreparedStatement預(yù)編譯對象,執(zhí)行SQL并獲取返回集合。

public class SimpleExecutor extends BaseExecutor {
    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();
            //獲取Statement的類型,即默認的PreparedStatementHandler
            //需要注意,在這里如果配置了插件,則StatementHandler可能返回的是一個代理
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //創(chuàng)建PreparedStatement對象,并設(shè)置參數(shù)值
            stmt = prepareStatement(handler, ms.getStatementLog());
            //執(zhí)行execute 并返回結(jié)果集
            return handler.<E>query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
}

prepareStatement方法獲取數(shù)據(jù)庫連接并構(gòu)建Statement對象設(shè)置SQL參數(shù)。

1、創(chuàng)建PreparedStatement

public class SimpleExecutor extends BaseExecutor {
    private Statement prepareStatement(StatementHandler handler, Log statementLog) {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }
}
  • 獲取Connection連接

我們看到getConnection方法就是獲取Connection連接的地方。但這個Connection也是一個代理對象,它的調(diào)用程序處理器為ConnectionLogger。顯然,它是為了更方便的打印日志。

public abstract class BaseExecutor implements Executor {
    protected Connection getConnection(Log statementLog) throws SQLException {
        //從c3p0連接池中獲取一個連接
        Connection connection = transaction.getConnection();
        //如果日志級別為Debug,則為這個連接生成代理對象返回
        //它的處理類為ConnectionLogger
        if (statementLog.isDebugEnabled()) {
            return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
            return connection;
        }
    }
}
  • 執(zhí)行預(yù)編譯

這個跟我們的JDBC代碼是一樣的,拿到SQL,調(diào)用Connection連接的prepareStatement(sql)。但由于connection是一個代理對象,似乎又沒那么簡單。

public class PreparedStatementHandler
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        return connection.prepareStatement(sql);
    }
}

所以,在執(zhí)行的onnection.prepareStatement(sql)的時候,實際調(diào)用的是ConnectionLogger類的invoke()。

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] params)throws Throwable {
        try {
            if ("prepareStatement".equals(method.getName())) {
                if (isDebugEnabled()) {
                    debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
                }        
                //調(diào)用connection.prepareStatement
                PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
                //又為stmt創(chuàng)建了代理對象,通知類為PreparedStatementLogger
                stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
                return stmt;
            }
        } 
    }
}

public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
    InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
    ClassLoader cl = PreparedStatement.class.getClassLoader();
    return (PreparedStatement) Proxy.newProxyInstance(cl, 
            new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}

果然沒那么簡單,最后返回的PreparedStatement又是個代理對象。

  • 設(shè)置參數(shù)

我們知道,在設(shè)置參數(shù)的時候,你有很多可選項,比如stmt.setString()、stmt.setInt()、stmt.setFloat()等,或者粗暴一點就stmt.setObject()

當然了,Mybatis作為一個優(yōu)秀的ORM框架,不可能這么粗暴。它先是根據(jù)參數(shù)的Java類型獲取所有JDBC類型的處理器,再根據(jù)JDBC的類型獲取對應(yīng)的處理器。在這里我們沒有配置JDBC類型,所以就是它的類型為NULL,最后返回的就是StringTypeHandler。

關(guān)于類型處理器的匹配和查詢規(guī)則,咱們在Mybatis源碼分析(三)通過實例來看typeHandlers已經(jīng)詳細分析過,就不再細看。

public class StringTypeHandler extends BaseTypeHandler<String> {
    public void setNonNullParameter(PreparedStatement ps, int i, 
            String parameter, JdbcType jdbcType)throws SQLException {
        ps.setString(i, parameter);
    }
}

2、執(zhí)行

在SQL預(yù)編譯完成之后,調(diào)用execute()執(zhí)行。

public class PreparedStatementHandler{
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.<E> handleResultSets(ps);
    }
}

這里的PreparedStatement對象也是個代理類,在調(diào)用通知類PreparedStatementLogger,執(zhí)行execute的時候,只是打印了參數(shù)的值。即Parameters: 501868995461251072(String)

五、處理返回值

上面的方法我們看到SQL已經(jīng)提交給數(shù)據(jù)庫執(zhí)行,那么最后一步就是獲取返回值。

public class DefaultResultSetHandler implements ResultSetHandler {
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        final List<Object> multipleResults = new ArrayList<Object>();
        int resultSetCount = 0;
        //將ResultSet封裝成ResultSetWrapper對象
        ResultSetWrapper rsw = getFirstResultSet(stmt);
        //返回mapper.xml中配置的rsultMap 實際上我們沒有配置,但會有默認的一個
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        //處理數(shù)據(jù)庫的返回值,最后加入到multipleResults
        while (rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = resultMaps.get(resultSetCount);
            handleResultSet(rsw, resultMap, multipleResults, null);
            resultSetCount++;
        }
        //返回
        return collapseSingleResultList(multipleResults);
    }
}

1、ResultSetWrapper對象

上面的代碼我們看到,第一步就把ResultSet對象封裝成了ResultSetWrapper對象,關(guān)于它還需要具體來看。

public class ResultSetWrapper {
    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
        //所有已注冊的類型處理器
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        //ResultSet對象
        this.resultSet = rs;
        //元數(shù)據(jù) 列名、列類型等信息
        final ResultSetMetaData metaData = rs.getMetaData();
        final int columnCount = metaData.getColumnCount();
        //循環(huán)列,將列名、列對應(yīng)的JDBC類型和列對應(yīng)的Java類型都獲取到
        for (int i = 1; i <= columnCount; i++) {
            columnNames.add(metaData.getColumnName(i));
            jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
            classNames.add(metaData.getColumnClassName(i));
        }
    }
}

上面的重點是拿到數(shù)據(jù)庫列上的信息,在解析的時候會用到。

2、處理返回值

handleResultSet方法最后調(diào)用到DefaultResultSetHandler.handleRowValuesForSimpleResultMap()

public class DefaultResultSetHandler implements ResultSetHandler {
    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, 
            ResultMap resultMap, ResultHandler<?> resultHandler, 
            RowBounds rowBounds, ResultMapping parentMapping)throws SQLException {
            
        DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
        //跳過行 Mybatis的RowBounds分頁功能
        skipRows(rsw.getResultSet(), rowBounds);
        while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
            ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
            Object rowValue = getRowValue(rsw, discriminatedResultMap);
            storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
    }
}

在這個地方涉及到Mybatis中分頁的一個對象RowBounds。但實際上,我們基本不會用到它。因為它是一個邏輯分頁,而非物理分頁。

  • RowBounds

RowBounds對象中有兩個屬性控制著分頁:offset、limit。offset是說分頁從第幾條數(shù)據(jù)開始,limit是說一共取多少條數(shù)據(jù)。因為我們沒有配置它,所以它默認是offset從0開始,limit取Int的最大值。

public class RowBounds {
    public static final int NO_ROW_OFFSET = 0;
    public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
    public RowBounds() {
        this.offset = NO_ROW_OFFSET;
        this.limit = NO_ROW_LIMIT;
    }
}

skipRows方法就是來跳過offset,它的實現(xiàn)也比較簡單。

private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    for (int i = 0; i < rowBounds.getOffset(); i++) {
        rs.next();
    }
}

offset跳過之后,怎么控制Limit的呢?這就要看上面的while循環(huán)了。

while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    //處理數(shù)據(jù)
}

關(guān)鍵在于shouldProcessMoreRows()方法,它其實是個簡單的判斷。

private boolean shouldProcessMoreRows(ResultContext<?> context, 
                            RowBounds rowBounds) throws SQLException {
    //就是看已經(jīng)取到的數(shù)據(jù)是否小與Limit
    return context.getResultCount() < rowBounds.getLimit();
}
  • 獲取

while循環(huán)獲取ResultSet的每一行數(shù)據(jù),然后通過rs.getxxx()獲取數(shù)據(jù)。

public class DefaultResultSetHandler implements ResultSetHandler {
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        //創(chuàng)建返回值類型,比如我們返回的是User實體類
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            final MetaObject metaObject = configuration.newMetaObject(rowValue);
            boolean foundValues = this.useConstructorMappings;
            if (shouldApplyAutomaticMappings(resultMap, false)) {
            //自動映射
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
        }
        //這個處理配置的ResultMap,就是手動配置數(shù)據(jù)庫列名與Java實體類字段的映射
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        return rowValue;
    }
}

1、獲取返回值類型

第一步是獲取返回值類型,過程就是拿到Class對象,然后獲取構(gòu)造器,設(shè)置可訪問并返回實例。

private  <T> T instantiateClass(Class<T> type, 
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Constructor<T> constructor;
    if (constructorArgTypes == null || constructorArgs == null) {
        //獲取構(gòu)造器
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
            //設(shè)置可訪問
            constructor.setAccessible(true);
        }
        //返回實例
        return constructor.newInstance();
    }
}

返回后,又把它包裝成了MetaObject對象。Mybatis會根據(jù)返回值類型的不同,包裝成不同的Wrapper對象。本例中,由于是一個實體類,會返回BeanWrapper。

private MetaObject(Object object, ObjectFactory objectFactory, 
            ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
        this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
        this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
        this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
        this.objectWrapper = new BeanWrapper(this, object);
    }
}

2、applyAutomaticMappings

在mapper.xml中我們可以聲明一個resultMap節(jié)點,將數(shù)據(jù)庫中列的名稱和Java中字段名稱對應(yīng)起來,應(yīng)用到SQL節(jié)點的resultMap中。也可以不配置它,直接利用resultType返回一個Bean即可。但是這兩種方式會對應(yīng)兩種解析方法。

private boolean applyAutomaticMappings(ResultSetWrapper rsw, 
        ResultMap resultMap, MetaObject metaObject, 
        String columnPrefix) throws SQLException {  
    //獲取相應(yīng)字段的類型處理器
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
        for (UnMappedColumnAutoMapping mapping : autoMapping) {
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            if (value != null) {
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                //因為返回值類型是一個BeanWapper,通過反射把值設(shè)置到JavaBean中。
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}
  • 獲取

上面代碼的重點是獲取對應(yīng)字段的類型處理器,調(diào)用對應(yīng)類型處理器的getResult方法從ResultSet中拿到數(shù)據(jù)的值。

//type是Java字段的類型 jdbcType是數(shù)據(jù)庫列的JDBC類型
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    //先從所有的處理器中獲取Java類型的處理器
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
        //再根據(jù)JDBC的類型獲取實際的處理器
        handler = jdbcHandlerMap.get(jdbcType);
    }
    return (TypeHandler<T>) handler;
}

以ID為例,在Java中是String類型,在數(shù)據(jù)庫中是VARCHAR,最后返回的類型處理器是StringTypeHandler。調(diào)用的時候,就很簡單了。

return rs.getString(columnName);
  • 設(shè)置

通過rs.getString()拿到值之后,然后向返回值類型中設(shè)置。因為我們返回的是一個JavaBean,對應(yīng)的是BeanWapper對象,方法中其實就是反射調(diào)用。

public class BeanWrapper extends BaseWrapper {
    private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
        try {
            Invoker method = metaClass.getSetInvoker(prop.getName());
            Object[] params = {value};
            try {
                method.invoke(object, params);
            } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
            }
        } catch (Throwable t) {
            throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
        }
    }
}

把所有的列都解析完,返回指定的Bean。最后加入到list,整個方法返回。在selectOne方法中,取List的第一條數(shù)據(jù)。如果數(shù)據(jù)記錄大于1,就是出錯了。

public class DefaultSqlSession implements SqlSession {
    public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) 
                          to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
}

六、總結(jié)

關(guān)于Mybatis執(zhí)行方法的整個過程,我們簡單歸納一下。

  • 獲取SqlSession,根據(jù)方法的返回值類型調(diào)用不同的方法。比如selectOne

  • 獲取BoundSql對象,根據(jù)傳遞的參數(shù)生成SQL語句

  • 從數(shù)據(jù)庫連接池獲取Connection對象,并為它創(chuàng)建代理,以便打印日志

  • 從Connection中獲取PreparedStatement預(yù)編譯對象,并為它創(chuàng)建代理

  • 預(yù)編譯SQL,并設(shè)置參數(shù)

  • 執(zhí)行、返回數(shù)據(jù)集合

  • 將數(shù)據(jù)集轉(zhuǎn)換為Java對象

看到這里,再回憶下我們開頭的JDBC實例的步驟,可以看到它們兩者之間的主流程都是一樣的。Mybatis只是在此基礎(chǔ)上做了一些封裝,更好的服務(wù)于我們的應(yīng)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內(nèi)容