Mybatis:执行查询

前面讲了创建SqlSession,注册Mapper接口,创建接口代理对象,终于来到执行查询的语句

接上文Demo:

public class MybatisDemo {
    public static void main(String[] args) {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 添加Mapper接口,并解析对应的XML文件
            session.getConfiguration().addMapper(BlogMapper.class);
            // 通过sqlSession获取Mapper接口代理对象
            BlogMapper blogMapper = session.getMapper(BlogMapper.class);
            // 通过代理对象调用接口
            blogMapper.selectBlog(101);
        }
    }
}

示例中BlogMapper接口采用JDK动态代理技术创建代理对象,那么这个代理对象调用的时候,必然会执行一个InvocationHandler#invoke,这是JDK动态代理技术实现增强代理的关键逻辑,Mybatis提供了MapperProxy,它实现了InvocationHandler接口,通过代理接口的方法,执行数据库操作

public class MapperProxy<T> implements InvocationHandler, Serializable {
    // Mapper接口方法的缓存信息
    private final Map<Method, MapperMethod> methodCache;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ...
        // 通过methodCache获取被拦截方法的解析信息
        // 如果缓存没有那么创建一个放入缓存并返回
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        // 从methodCache获取MapperMethod,
        // 如果没有就创建一个存到methodCache,并返回
        return methodCache.computeIfAbsent(method, 
                k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
}

MapperProxy中,使用一个Map集合缓存接口方法的解析信息MapperMethod对象,并通过它执行后续操作

public class MapperMethod {
    private final SqlCommand command;
    private final MethodSignature method;
    
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        // SqlCommand记录了当前方法执行的方法名称和操作类型
        this.command = new SqlCommand(config, mapperInterface, method);
        // MethodSignature记录了当前调用方法入参、出参信息
        this.method = new MethodSignature(config, mapperInterface, method);
    }
}

SqlCommand记录了当前接口的方法名称,以及数据库操作类型等信息

public static class SqlCommand {

    private final String name;  // 记录名称,如com.holybell.mapper.BlogMapper.selectBlog
    private final SqlCommandType type; // 操作类型

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
        final String methodName = method.getName();
        final Class<?> declaringClass = method.getDeclaringClass();
        // 如果当前接口没有解析XML文件,这里会尝试重新解析
        MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, 
                                                    declaringClass, configuration);
        if (ms == null) {
            // ...
        } else {
            name = ms.getId();
            type = ms.getSqlCommandType();
            if (type == SqlCommandType.UNKNOWN) {
                // 省略抛异常...
            }
        }
    }
}

MethodSignature记录了当前调用方法入参、出参等信息

  public static class MethodSignature {

    private final boolean returnsMany;   // 是否返回多个
    private final boolean returnsMap;    // 是否返回resultMap
    private final boolean returnsVoid;    // 是否返回空
    private final boolean returnsCursor;// 是否返回游标
    private final boolean returnsOptional;
    private final Class<?> returnType;  // 返回结果类型
    // 入参解析器 保存了接口入参下标  -> 入参名称 的关系映射
    private final ParamNameResolver paramNameResolver;  

}

回到MapperProxy,执行MapperMethod#execute,来到如下方法,可以看到它根据SqlCommand记录的操作类型执行不同的操作,最终还是应用SqlSession做进一步的执行

public class MapperMethod {

    private final SqlCommand command;
    private final MethodSignature method;

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                // ...
                break;
            }
            case UPDATE: {
                // ...
                break;
            }
            case DELETE: {
                // ...
                break;
            }
            case SELECT:
                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 if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    // 本例会执行这个分支
                    // 通过MethodSignature缓存的方法信息解析入参
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 最终还是应用SqlSession发起数据库操作
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional()
                            && (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                // ... 省略异常抛出
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            // ... 省略异常抛出
        }
        return result;
    }
}

执行SqlSession#selectOne,跟进它会来到DefaultSqlSession#selectList,Mybatis中执行查询一条记录的selectOne最终也是通过查询多条记录的selectList实现,只是它在查询一条记录的时候,返回列表第一个元素

public class DefaultSqlSession implements SqlSession {

    private final Executor executor;

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 从Configuration中获取解析XML中生成的MappedStatement 
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, wrapCollection(parameter), rowBounds, 
                                  Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            // ...省略异常处理
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

接着,Mybatis抽象出一个数据库操作执行器Executor,随着调用的深入,来到CachingExecutor,它应用装饰器模式组合了一个名为delegateExecutor对象,顾名思义为其他Executor增加从缓存查询的功能,这是Mybatis的二级缓存

public class CachingExecutor implements Executor {

    private final Executor delegate;

    public <E> List<E> query(MappedStatement ms, Object parameterObject, 
            RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取解析好的SQL,将#{}之类的占位符替换为?,记录每个可变参数的信息
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 生成Mybatis缓存的key
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    public <E> List<E> query(MappedStatement ms, Object parameterObject, 
            RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, 
            BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) tcm.getObject(cache, key);
                // 如果缓存没有查询到,委托给delegate执行查询
                if (list == null) {
                    list = delegate.query(ms, parameterObject, rowBounds, 
                            resultHandler, key, boundSql);
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        // 如果不能使用二级缓存,委托给delegate执行查询
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

如果通过CachingExecutorMybatis未能从二级缓存中获取到数据,只能通过被它装饰的delegate对象执行数据库查询操作,来到被装饰的SimpleExecutor执行器中,它继承了BaseExecutor,在BaseExecutor中Mybatis再次尝试从一级缓存查询数据,如果没有查询到最终才会去执行数据库操作

public abstract class BaseExecutor implements Executor {
    // 一级缓存
    protected PerpetualCache localCache;

    public <E> List<E> query(MappedStatement ms, Object parameter, 
            RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, 
            BoundSql boundSql) throws SQLException {
        // ...
        List<E> list;
        try {
            // ...
            // 尝试从一级缓存加载数据
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 对一级缓存的参数进行处理
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 从缓存中没有找到,则从数据库中查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // ...
        }
        // ...
        return list;
    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, 
            RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, 
            BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 从这里开始执行数据库操作,这是一个抽象方法,模板模式的应用
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            localCache.removeObject(key);
        }
        // 维护一级缓存
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }
    // 具体执行查询的方法,交由子类实现
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, 
            RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException;
}

一级缓存中仍然没有获取到数据,接着执行BaseExecutor#queryFromDatabase,进而执行了子类SimpleExecutor#doQuery方法,在这里会创建一个StatementHandler处理器处理JDBC原生的Statement对象

public class SimpleExecutor extends BaseExecutor {

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, 
            RowBounds rowBounds, ResultHandler resultHandler, 
            BoundSql boundSql) throws SQLException {
        // 来到这里,看到Statement应该很熟悉了,这就是JDBC里面的Statement
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 通过Configuration返回一个RoutingStatementHandler对象
            // 创建StatementHandler的同时会为它执行拦截器调用进行增强
            StatementHandler handler = configuration.newStatementHandler(
                    wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 在这里会执行JDBC中为SQL中的?设置具体参数值操作
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }

    private Statement prepareStatement(StatementHandler handler, Log statementLog) 
            throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 如果是PreparedStatement为?占位符设置属性
        handler.parameterize(stmt);
        return stmt;
    }
}

上述代码中创建StatementHandler的逻辑,包含对它执行拦截器操作

public class Configuration {

    public StatementHandler newStatementHandler(Executor executor, 
            MappedStatement mappedStatement, Object parameterObject, 
            RowBounds rowBounds, ResultHandler resultHandler, 
            BoundSql boundSql) {

        StatementHandler statementHandler 
                = new RoutingStatementHandler(executor, mappedStatement, 
                        parameterObject, rowBounds, resultHandler, boundSql);
        // 拦截器执行处
        statementHandler 
                = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
}

SimpleExecutor#doQuery创建的StatementHandler对象是RoutingStatementHandler,它实现了StatementHandler,跟CachingExecutor一样,它也组合了一个实现了相同接口的delegate对象,然后根据要执行不同的操作类型,生成SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler之一

public class RoutingStatementHandler implements StatementHandler {

    private final StatementHandler delegate;  

    // 根据不同的StatementType,生成不同的delegate对象
    // 具体的操作交由delegate执行
    public RoutingStatementHandler(Executor executor, MappedStatement ms, 
            Object parameter, RowBounds rowBounds, ResultHandler resultHandler, 
            BoundSql boundSql) {

        switch (ms.getStatementType()) {
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, 
                        rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                delegate = new PreparedStatementHandler(executor, ms, parameter, 
                        rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                delegate = new CallableStatementHandler(executor, ms, parameter, 
                        rowBounds, resultHandler, boundSql);
                break;
            default:
                // ...省略异常
        }
    }

    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) 
            throws SQLException {
        return delegate.prepare(connection, transactionTimeout);
    }

    @Override
    public void parameterize(Statement statement) throws SQLException {
        delegate.parameterize(statement);
    }
    // ...
}

如果使用了#{}占位符,Mybatis会执行PREPARED类型的PreparedStatementHandler,通过DefaultParameterHandler为SQL语句设置参数值

public class PreparedStatementHandler extends BaseStatementHandler {
    // ... 
    protected final ParameterHandler parameterHandler;  // 参数处理器
    // ... 
    @Override
    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }
}

DefaultParameterHandler使用TypeHandler为?占位符设置属性值

public class DefaultParameterHandler implements ParameterHandler {
    public void setParameters(PreparedStatement ps) {
        // ...
        try {
            // 设置preparedStatement的参数
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
            // 省略异常...
        }
    }
}

继续往下,来到Mybatis的TypeHandler,通过它将不同的类型的参数值设置到SQL语句的?占位符上,由于本例使用整型参数ID,因此采用IntegerTypeHandler

blogMapper.selectBlog(101);
public abstract class BaseTypeHandler<T> 
        extends TypeReference<T> implements TypeHandler<T> {

    public void setParameter(PreparedStatement ps, int i, T parameter, 
            JdbcType jdbcType) throws SQLException {

        if (parameter == null) {
            // ...
        } else {
            try {
                // 这是个抽象方法,根据不同的类型,有不同的TypeHandler
                setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception e) {
                // ...
            }
        }
    }    
}
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
    // 设置Integer类型的参数
    public void setNonNullParameter(PreparedStatement ps, int i, 
            Integer parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter);
    }
}

为SQL语句设置完参数,再次回到SimpleExecutor#doQuery开始执行数据库查询,它又通过前面讨论过的RoutingStatementHandler装饰的PreparedStatementHandler执行查询

public class SimpleExecutor extends BaseExecutor {

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, 
            RowBounds rowBounds, ResultHandler resultHandler, 
            BoundSql boundSql) throws SQLException {
        // 来到这里,看到Statement应该很熟悉了,这就是JDBC里面的Statement
        Statement stmt = null;
        try {
            // ...
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
}

RoutingStatementHandler委托它装饰的delegate对象执行操作

public class RoutingStatementHandler implements StatementHandler {

    private final StatementHandler delegate;

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) 
            throws SQLException {
        return delegate.query(statement, resultHandler);
    }
}

PreparedStatementHandler实现了StatementHandler接口,通过它调用JDBC的PreparedStatement执行数据库操作,之后就是数据库驱动完成的逻辑了

public class PreparedStatementHandler extends BaseStatementHandler {
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) 
            throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();   // 执行数据库操作
        return resultSetHandler.handleResultSets(ps);
  }
}

至此,Mybatis已经完成数据的查询操作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容