Mybatis源码分析(五)探究SQL语句的执行过程

一、重温JDBC

Java Database Connectivity,简称JDBC。是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
随着Java ORM框架的发展,已经很少有机会再在生产系统中写JDBC的代码来访问数据库了,但是基本流程我们还是要熟悉。下面以一个简单的查询为例,温故一下JDBC。

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);
    }
}

从上面的代码来看,一次简单的数据库查询操作,可以分为几个步骤。

  • 创建Connection连接

  • 传入参数化查询SQL语句构建预编译对象PreparedStatement

  • 设置参数

  • 执行SQL

  • 从结果集中获取数据

那么,咱们的主角Mybatis是怎样完成这一过程的呢?不着急,咱们一个一个来看。

二、sqlSession

在上一章节的内容中,我们已经看到了在Service层通过@Autowired注入的userMapper是个代理类,在执行方法的时候实际上调用的是代理类的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 、创建MapperMethod对象

MapperMethod对象里面就两个属性,SqlCommand和MethodSignature。

SqlCommand包含了执行方法的名称和方法的类型,比如UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
MethodSignature可以简单理解为方法的签名信息。里面包含:返回值类型、是否void、是否为集合类型、是否为Cursor等,主要还获取到了方法参数上的@Param注解的名称,方便下一步获取参数值。
比如,如果方法上加了@Param的参数:
User getUserById(@Param(value="id")String id,@Param(value="password")String password);,参数会被解析成{0=id, 1=password}

2、执行

判断方法的SQL类型和返回值类型 ,调用相应的方法。以方法User getUserById(String id,String password)为例,会调用到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 {
            //将参数args转换为SQL命令的参数
            //默认会添加一个《param+参数索引》的参数名
            //{password=123456, id=501441819331002368, param1=501441819331002368, param2=123456}
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
        }
        ...
        return result;
    }
}

可以看到,sqlSession.selectOne就可以获取到数据库中的值并完成转换工作。这里的sqlSession就是SqlSessionTemplate实例的对象,所以它会调用到

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

sqlSessionProxy也是个代理对象。关于它的创建咱们上节课也很认真的分析了,总之它实际会调用到SqlSessionInterceptor.invoke()

3、创建sqlSession对象

sqlSession我们熟悉呀,它作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能。关于它的创建、执行、提交和资源清理都是在SqlSessionInterceptor的通知方法中完成的。

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        //创建SqlSession对象
        SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
        try {
            //调用sqlSession实际方法
            Object result = method.invoke(sqlSession, args);
            return result;
        } catch (Throwable t) {
            ....
        } finally {
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

上面的重点就是创建了SqlSession并执行它的方法,它是一个DefaultSqlSession实例的对象,里面主要有一个通过configuration创建的执行器,在这里它是SimpleExecutor。

那么,invoke方法实际调用的就是DefaultSqlSession.selectOne()

三、获取BoundSql对象

DefaultSqlSession中的selectOne()方法最终也会调用到selectList()方法。它先从数据大管家configuration中根据请求方法的全名称拿到对应的MappedStatement对象,然后调用执行器的查询方法。

1、获取MappedStatement对象

//statement是调用方法的全名称,parameter为参数的Map
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    //在mapper.xml中每一个SQL节点都会封装为MappedStatement对象
    //在configuration中就可以通过请求方法的全名称获取对应的MappedStatement对象
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}

其中有个方法wrapCollection(parameter)我们可以了解下,如果参数为集合类型或者数组类型,它会将参数名称设置为相应类型的名称。

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节点。每一个节点对应一个MappedStatement对象,而动态生成的各种sqlNode保存在SqlSource对象,SqlSource对象有一个方法就是getBoundSql()。
我们先来看一下BoundSql类哪有哪些属性。

public class BoundSql { 
    //动态生成的SQL,解析完毕带有占位性的SQL
    private final String sql;
    //每个参数的信息。比如参数名称、输入/输出类型、对应的JDBC类型等
    private final List<ParameterMapping> parameterMappings;
    //参数
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;
}

看到这几个属性,也就解释了BoundSql 的含义。即表示动态生成的SQL语句和相应的参数信息。

不知大家是否还有印象,不同类型的SQL会生成不同类型的SqlSource对象。比如静态SQL会生成StaticSqlSource对象,动态SQL会生成DynamicSqlSource对象。

  • 静态SQL

静态SQL比较简单,直接就创建了BoundSql对象并返回。

public class StaticSqlSource implements SqlSource {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
  • 动态SQL

动态SQL要根据不同的sqlNode节点,调用对应的apply方法,有的还要通过Ognl表达式来判断是否需要添加当前节点,比如IfSqlNode。

public class DynamicSqlSource implements SqlSource {
    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        //rootSqlNode为sqlNode节点的最外层封装,即MixedSqlNode。
        //解析完所有的sqlNode,将sql内容设置到context
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        //设置参数信息 将SQL#{}替换为占位符
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        //创建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)是一个迭代调用的过程。最后生成的内容保存在DynamicContext对象,比如select * from user WHERE uid=#{uid}

然后调用SqlSourceBuilder.parse()方法。它主要做了两件事:

1、将SQL语句中的#{}替换为占位符
2、将#{}里面的字段封装成ParameterMapping对象,添加到parameterMappings。

ParameterMapping对象保存的就是参数的类型信息,如果没有配置则为null。
ParameterMapping{property='uid', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}

最后返回的BoundSql对象就包含一个带有占位符的SQL和参数的具体信息。

四、执行SQL

创建完BoundSql对象,调用query方法,来到CachingExecutor.query()。这个方法的前面是二级缓存的判断,如果开启了二级缓存且缓存中有数据,就返回。

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 {
        //二级缓存的应用
        //如果配置</cache>则走入这个流程
        Cache cache = ms.getCache();
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                //从缓存中获取数据
                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方法,创建PreparedStatement预编译对象,执行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);
            //创建PreparedStatement对象,并设置参数值
            stmt = prepareStatement(handler, ms.getStatementLog());
            //执行execute 并返回结果集
            return handler.<E>query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
}

prepareStatement方法获取数据库连接并构建Statement对象设置SQL参数。

1、创建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也是一个代理对象,它的调用程序处理器为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;
        }
    }
}
  • 执行预编译

这个跟我们的JDBC代码是一样的,拿到SQL,调用Connection连接的prepareStatement(sql)。但由于connection是一个代理对象,似乎又没那么简单。

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

所以,在执行的onnection.prepareStatement(sql)的时候,实际调用的是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);
                }        
                //调用connection.prepareStatement
                PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
                //又为stmt创建了代理对象,通知类为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又是个代理对象。

  • 设置参数

我们知道,在设置参数的时候,你有很多可选项,比如stmt.setString()、stmt.setInt()、stmt.setFloat()等,或者粗暴一点就stmt.setObject()

当然了,Mybatis作为一个优秀的ORM框架,不可能这么粗暴。它先是根据参数的Java类型获取所有JDBC类型的处理器,再根据JDBC的类型获取对应的处理器。在这里我们没有配置JDBC类型,所以就是它的类型为NULL,最后返回的就是StringTypeHandler。

关于类型处理器的匹配和查询规则,咱们在Mybatis源码分析(三)通过实例来看typeHandlers已经详细分析过,就不再细看。

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

2、执行

在SQL预编译完成之后,调用execute()执行。

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

这里的PreparedStatement对象也是个代理类,在调用通知类PreparedStatementLogger,执行execute的时候,只是打印了参数的值。即Parameters: 501868995461251072(String)

五、处理返回值

上面的方法我们看到SQL已经提交给数据库执行,那么最后一步就是获取返回值。

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();
        //处理数据库的返回值,最后加入到multipleResults
        while (rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = resultMaps.get(resultSetCount);
            handleResultSet(rsw, resultMap, multipleResults, null);
            resultSetCount++;
        }
        //返回
        return collapseSingleResultList(multipleResults);
    }
}

1、ResultSetWrapper对象

上面的代码我们看到,第一步就把ResultSet对象封装成了ResultSetWrapper对象,关于它还需要具体来看。

public class ResultSetWrapper {
    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
        //所有已注册的类型处理器
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        //ResultSet对象
        this.resultSet = rs;
        //元数据 列名、列类型等信息
        final ResultSetMetaData metaData = rs.getMetaData();
        final int columnCount = metaData.getColumnCount();
        //循环列,将列名、列对应的JDBC类型和列对应的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));
        }
    }
}

上面的重点是拿到数据库列上的信息,在解析的时候会用到。

2、处理返回值

handleResultSet方法最后调用到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是说分页从第几条数据开始,limit是说一共取多少条数据。因为我们没有配置它,所以它默认是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,它的实现也比较简单。

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

offset跳过之后,怎么控制Limit的呢?这就要看上面的while循环了。

while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    //处理数据
}

关键在于shouldProcessMoreRows()方法,它其实是个简单的判断。

private boolean shouldProcessMoreRows(ResultContext<?> context, 
                            RowBounds rowBounds) throws SQLException {
    //就是看已经取到的数据是否小与Limit
    return context.getResultCount() < rowBounds.getLimit();
}
  • 获取

while循环获取ResultSet的每一行数据,然后通过rs.getxxx()获取数据。

public class DefaultResultSetHandler implements ResultSetHandler {
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        //创建返回值类型,比如我们返回的是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,就是手动配置数据库列名与Java实体类字段的映射
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        return rowValue;
    }
}

1、获取返回值类型

第一步是获取返回值类型,过程就是拿到Class对象,然后获取构造器,设置可访问并返回实例。

private  <T> T instantiateClass(Class<T> type, 
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Constructor<T> constructor;
    if (constructorArgTypes == null || constructorArgs == null) {
        //获取构造器
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
            //设置可访问
            constructor.setAccessible(true);
        }
        //返回实例
        return constructor.newInstance();
    }
}

返回后,又把它包装成了MetaObject对象。Mybatis会根据返回值类型的不同,包装成不同的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节点,将数据库中列的名称和Java中字段名称对应起来,应用到SQL节点的resultMap中。也可以不配置它,直接利用resultType返回一个Bean即可。但是这两种方式会对应两种解析方法。

private boolean applyAutomaticMappings(ResultSetWrapper rsw, 
        ResultMap resultMap, MetaObject metaObject, 
        String columnPrefix) throws SQLException {  
    //获取相应字段的类型处理器
    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,通过反射把值设置到JavaBean中。
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}
  • 获取

上面代码的重点是获取对应字段的类型处理器,调用对应类型处理器的getResult方法从ResultSet中拿到数据的值。

//type是Java字段的类型 jdbcType是数据库列的JDBC类型
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    //先从所有的处理器中获取Java类型的处理器
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
        //再根据JDBC的类型获取实际的处理器
        handler = jdbcHandlerMap.get(jdbcType);
    }
    return (TypeHandler<T>) handler;
}

以ID为例,在Java中是String类型,在数据库中是VARCHAR,最后返回的类型处理器是StringTypeHandler。调用的时候,就很简单了。

return rs.getString(columnName);
  • 设置

通过rs.getString()拿到值之后,然后向返回值类型中设置。因为我们返回的是一个JavaBean,对应的是BeanWapper对象,方法中其实就是反射调用。

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的第一条数据。如果数据记录大于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;
        }
    }
}

六、总结

关于Mybatis执行方法的整个过程,我们简单归纳一下。

  • 获取SqlSession,根据方法的返回值类型调用不同的方法。比如selectOne

  • 获取BoundSql对象,根据传递的参数生成SQL语句

  • 从数据库连接池获取Connection对象,并为它创建代理,以便打印日志

  • 从Connection中获取PreparedStatement预编译对象,并为它创建代理

  • 预编译SQL,并设置参数

  • 执行、返回数据集合

  • 将数据集转换为Java对象

看到这里,再回忆下我们开头的JDBC实例的步骤,可以看到它们两者之间的主流程都是一样的。Mybatis只是在此基础上做了一些封装,更好的服务于我们的应用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容