Mybatis源码阅读(四):核心接口4.1——StatementHandler

前言

难得复工了,公司百业待兴,有一大堆接口需要对接,忙的不行。回过神来发现自己快一个月没写博客了,赶紧抽时间写一写,不能断更。

截止上一篇博客,我们已经把结果集映射的内容介绍完毕,接下来就是对Mybatis中的核心接口进行介绍,通过介绍这些核心接口,使读者们更深刻地理解Mybatis的运行机制以及原理。

StatementHandler

StatementHandler接口是Mybatis的核心接口之一,它完成了Mybatis中最核心的工作,也是Executor接口实现的基础。

StatementHandler接口中功能有很多,如创建Statement对象、执行SQL语句、批量执行SQL语句等。StatementHandler接口定义如下。

/**
 * Mybatis核心接口之一,完成了Mybatis中最核心的工作,也是Executor接口实现的基础。
 *
 * @author Clinton Begin
 */
public interface StatementHandler {

    /**
     * 从数据库连接中获取一个Statement
     *
     * @param connection
     * @param transactionTimeout
     * @return
     * @throws SQLException
     */
    Statement prepare(Connection connection, Integer transactionTimeout)
            throws SQLException;

    /**
     * 绑定statement执行时所需的实参
     *
     * @param statement
     * @throws SQLException
     */
    void parameterize(Statement statement)
            throws SQLException;

    /**
     * 批量执行sql语句
     *
     * @param statement
     * @throws SQLException
     */
    void batch(Statement statement)
            throws SQLException;

    /**
     * 执行 update/insert/delete语句
     *
     * @param statement
     * @return
     * @throws SQLException
     */
    int update(Statement statement)
            throws SQLException;

    /**
     * 执行select语句
     *
     * @param statement
     * @param resultHandler
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> List<E> query(Statement statement, ResultHandler resultHandler)
            throws SQLException;

    /**
     * 查询游标
     *
     * @param statement
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> Cursor<E> queryCursor(Statement statement)
            throws SQLException;

    /**
     * 获取BoundSql
     *
     * @return
     */
    BoundSql getBoundSql();

    /**
     * 获取ParameterHandler
     *
     * @return
     */
    ParameterHandler getParameterHandler();

}

该接口的继承关系如下。其中,CallableStatementHandler用于调用存储过程,而mysql的存储过程在大多数公司都很少使用甚至禁止使用,这里就不对其进行介绍了,有兴趣的朋友可以自己参考源码阅读

RoutingStatementHandler

RoutingStatementHandler使用策略模式,根据MappedStatement中指定的statementType字段,创建对应的StatementHandler接口实现。由于其这种思路,还有人认为这是个路由。RoutingStatementHandler核心代码如下。

public class RoutingStatementHandler implements StatementHandler {

    /**
     * 底层封装的真正的StatementHandler对象
     * 这里的delegate在写框架的过程中使用较多
     */
    private final StatementHandler delegate;

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // RoutingStatementHandler的主要功能是根据MappedStatement的配置,生成对应的StatementHandler对象
        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:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }

    }

}

BaseStatementHandler

BaseStatementHandler是一个抽象类,实现了StatementHandler接口。它只提供了一些参数绑定相关的方法,对数据库不进行任何操作。该类的字段如下。

    protected final Configuration configuration;
    protected final ObjectFactory objectFactory;
    protected final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * 记录结果集映射对象
     */
    protected final ResultSetHandler resultSetHandler;

    /**
     * 记录使用的参数处理器对象。ParameterHandler的主要功能是为SQL绑定实参
     * 也就是使用传入的实参替换SQL语句中的 ? 占位符
     */
    protected final ParameterHandler parameterHandler;

    protected final Executor executor;

    /**
     * 记录SQL语句对应的MappedStatement
     */
    protected final MappedStatement mappedStatement;

    /**
     * 记录用户设置的offset和limit,用于在结果集中定位映射的起始位置和结束位置
     */
    protected final RowBounds rowBounds;

    protected BoundSql boundSql;

在BaseStatementHandler的构造方法中,除对上面的字段进行初始化之外,还会调用KeyGenerator.processBefore()方法初始化SQL的主键,具体实现如下。

    protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        this.configuration = mappedStatement.getConfiguration();
        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;

        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = configuration.getObjectFactory();

        if (boundSql == null) {
            // 调用KeyGenerator.processBefore 方法获取主键
            generateKeys(parameterObject);
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }

        this.boundSql = boundSql;

        this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    }

    protected void generateKeys(Object parameter) {
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        ErrorContext.instance().store();
        keyGenerator.processBefore(executor, mappedStatement, null, parameter);
        ErrorContext.instance().recall();
    }

BaseStatementHandler实现了StatementHandler的prepare方法,该方法用来初始化Statement对象,然后为其分配超时时间等属性。其中,初始化StatementHandler的方法是个抽象方法instantiateStatement,是一个抽象方法,需要由子类去实现,代码如下。

    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
            // 初始化statement对象,交给子类去实现
            statement = instantiateStatement(connection);
            // 设置超时时间
            setStatementTimeout(statement, transactionTimeout);
            setFetchSize(statement);
            return statement;
        } catch (SQLException e) {
            closeStatement(statement);
            throw e;
        } catch (Exception e) {
            closeStatement(statement);
            throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
        }
    }

BaseStatementHandler依赖两个重要的组件,分别是ParameterHandler和ResultSetHandler。后者在前面的文章中已经介绍,不在重复。

ParameterHandler

通过前面介绍动态SQL可知,在BoundSql中记录的SQL语句可能包含?占位符,每个?占位符都对应了BoundSql.parameterMapings集合中的一个元素。在ParameterHandler中只定义了一个setParameter方法,该方法用于为SQL语句绑定实参。ParameterHandler接口只有唯一一个实现类 DefaultParameterHandler,核心字段如下。

    /**
     * 管理Mybatis中的全部TypeHandler对象
     */
    private final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * SQL节点
     */
    private final MappedStatement mappedStatement;

    /**
     * 用户传入的实参对象
     */
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

DefaultParameterHandler的setParameters方法中会遍历parameterMappings集合中记录的ParameterMapping对象,并根据其中记录的参数名称找到对应的实参,再与SQL绑定。setParameters方法如下。

    /**
     * 设置参数
     * @param ps
     */
    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 取出参数映射列表
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            // 遍历参数
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                // OUT是存储过程中的输出参数,这里需要过滤掉这些参数
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    // 获取参数名称
                    String propertyName = parameterMapping.getProperty();
                    // 获取对应的实参值
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        // 实参可以直接通过TypeHandler转换成jdbcType
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 获取parameterMapping中设置的TypeHandler对象
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        // 为语句绑定实参
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }

对SQL语句绑定完实参后,就可以调用Statement对象的execute方法执行SQL了。

SimpleStatementHandler

SimpleStatementHandler是BaseStatementHandler的子类,底层使用了Statement对象完成数据库的相关操作,所以SQL语句中不能存在占位符,因此parameterize方法是空实现。

SimpleStatementHandler的instantiateStatement方法直接通过JDBC Connection创建Statement,代码如下。

    /**
     * 创建statement对象
     * @param connection
     * @return
     * @throws SQLException
     */
    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
            return connection.createStatement();
        } else {
            return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }

query方法完成了数据库查询的操作,并通过ResultSetHandler将结果集映射成结果对象,代码如下。

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        statement.execute(sql);
        return resultSetHandler.handleResultSets(statement);
    }

update方法负责执行insert、update、delete的SQL语句,并根据配置的KeyGenerator获取数据库生成的主键,具体实现如下。

    /**
     * 负责执行insert、update、delete语句
     * @param statement
     * @return
     * @throws SQLException
     */
    @Override
    public int update(Statement statement) throws SQLException {
        String sql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        int rows;
        if (keyGenerator instanceof Jdbc3KeyGenerator) {
            statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
            rows = statement.getUpdateCount();
            keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
        } else if (keyGenerator instanceof SelectKeyGenerator) {
            statement.execute(sql);
            rows = statement.getUpdateCount();
            keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
        } else {
            statement.execute(sql);
            rows = statement.getUpdateCount();
        }
        return rows;
    }

PreparedStatementHandler

该类底层依赖于PrepareStatement对象完成数据库的操作,instantiateStatement方法直接调用Connection的prepareStatement方法创建PrepareStatement对象,代码如下。

    /**
     * 直接调用Connection的prepareStatement方法创建PrepareStatement对象
     * @param connection
     * @return
     * @throws SQLException
     */
    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        // 获取待执行的sql
        String sql = boundSql.getSql();
        // 根据keyGenerator的值创建PrepareStatement对象
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
            String[] keyColumnNames = mappedStatement.getKeyColumns();
            if (keyColumnNames == null) {
                // 返回数据库生成的主键
                return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
            } else {
                // 在insert语句执行完成之后,将keyColumnNames指定的列返回
                return connection.prepareStatement(sql, keyColumnNames);
            }
        } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
            // 设置结果集是否可以滚动以及游标是否可以上下移动,设置结果集是否可更新
            return connection.prepareStatement(sql);
        } else {
            // 创建普通的PrepareStatement对象
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }

PrepareStatement的其他方法的实现与SimpleStatementHandler对应的方法实现类型,这里就不赘述了。

结语

最后宣传一下技术交流群,如果你觉得该博客对你有帮助,不放动动手指加一下交流群。

发布了35 篇原创文章 · 获赞 45 · 访问量 6134

猜你喜欢

转载自blog.csdn.net/qq_36403693/article/details/105116309
今日推荐