zoukankan      html  css  js  c++  java
  • MyBatis详细源码解析(下篇)

    第四步

    这一步的主要目的就是通过SqlSession执行SQL语句。

    Payment payment = sqlSession.selectOne("com.gzy.mybatistest.mapper.PaymentMapper.getPaymentById", 1);
    

    直接进入DefaultSqlSession类中selectOne方法,我们发现它调用了selectList方法,其实查询一个或者多个都是调用selectList方法,我们再进入selectList方法中。

    selectList方法从Configuration对象的mappedStatements集合中取出对应的MappedStatement对象,我们调用selectOne方法传入的参数是SQL的全限定方法名和SQL参数,在这行代码中第一个参数值是“com.gzy.mybatistest.mapper.PaymentMapper.getPaymentById” ,第二个参数值是要查询的Id值。

    我们回忆一下,MyBatis初始化的时候是不是把每个SQL标签解析成一个个的MapperStatement对象,并且把这些MapperStatement对象装进Configuration对象维护的一个Map集合中,这个Map集合的Key值就是SQL标签的全限定方法名,Value是对应的MapperStatement对象,我们之前说装进集合中备用就是在这里用的,这里就通过要调用的方法的全限定名从Map集合中取出对应的MapperStatement对象。

    比如我们现在selectOne方法调用的的是getPaymentById这个方法,所以现在通过它的全限定名“com.gzy.mybatistest.mapper.PaymentMapper.getPaymentById” 这个Key值从Configuration维护的Map集合中取出对应的MapperStatement对象。为什么要取出这个对象呢?因为MyBatis把每个SQL标签的所有数据都封装在了MapperStatement对象中,比如输入值类型、输入值、输出值类型、输出值以及SQL语句等。

    public <T> T selectOne(String statement, Object parameter) {
        // 调用下面的selectList方法
        List<T> list = this.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;
        }
    }
    
    public <E> List<E> selectList(String statement, Object parameter) {
        // 调用重载方法
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    
    // 最终调用
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 通过要调用方法的全限定名获取对应的MappedStatement对象
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 执行器调用父类BaseExecutor的query方法
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    

    Executor接口先走的是CachingExcutor缓存执行器,我们研究一下代码,我们看第二段代码他一开始从MapperStatement中获取BoundSql这个对象,因为真正的SQL语句封装在这个对象中,而且这个对象也负责把SQL中的占位符替换成我们传的参数,只是MapperStatement维护了BoundSql的引用而已。

    然后我们继续看createCacheKey方法,这个方法的意思就是根据这些参数生成一个缓存Key,当我们调用同一个SQL并且传的参数是一样的时候,生成的缓存Key是相同的。

    然后我们看到第二个query重载方法,它一开始就是获取二级缓存,因为没有开启二级缓存,所以这里为null,它查询缓存为null,就会走最后一句代码,调用代理Executor(即SimpleExecutor类)的query方法。

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        // 调用下面的query重载方法
        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);
                if (list == null) {
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    

    随后Executor接口调用父类BaseExecutor的query方法来查询数据库,MapperStatement被当做参数传入query方法,这个query方法是执行器调用的,我们知道执行器的作用是SQL的生成执行和查询缓存等操作,在这个query方法中我们会查询缓存和执行SQL语句,我们进入query方法。

    在第二个query重载方法一开始就声明了一个List集合,因为resultHandler参数传入的是null,所以会通过我们之前创建的缓存Key去本地缓存localCache中查询是否有缓存。如果list结果集不是null就处理一下缓存数据直接返回list结果集,如果没有缓存,他会从数据库中查询,看他这名字起的一看就知道是什么意思queryFromDatabase。

    // BaseExecutor类
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 通过MappedStatement类中的SqlSource对象获取存有SQL语句以及参数的BoundSql对象
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 获取缓存的Key值
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            queryStack++;
            // 先从缓存中获取数据并赋值给结果集
            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 {
            queryStack--;
        }
        if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }
    

    假设目前是第一次查询还没有缓存,会进入queryFromDatabase方法。

    最后进入到SimpleExecutor类的doQuery方法,doQuery方法一开始就通过Configuration对象创建了一个会话处理器StatementHandler,会话处理器我们上面的组件介绍提到过,作用是封装了JDBC Statement的操作并且负责对JDBC Statement的操作,如设置参数等。

    // BaseExecutor类
    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 {
            // 然后再调用子类中的doQuery查询方法
            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;
    }
    

    到了这一步,我们终于接触到原生JDBC API了!

    StatementHandler有三个常用子类:

    1. SimpleStatementHandler:对应Statement。
    2. PreparedStatementHandler:对应PreparedStatement。
    3. CalledStatementHandler:对应CalledStatement。

    那我们现在复习一下Jdbc操作数据库的步骤:

    1. 注册驱动。
    2. 获取连接。
    3. 创建会话对象:也就是上面提到的Statement或者是PrepareStatement。
    4. 执行SQL语句。
    5. 处理结果集。
    6. 关闭连接。
    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();
            // 通过Configuration对象创建了一个会话处理器StatementHandler
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 其实本质还是调用的Statement或PreparedStatement的查询方法
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    

    我们进入prepareStatement方法中,可以看到全都是熟悉的JDBC步骤!下面我们逐步分析这三行代码。

    // SimpleExecutor类
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 获取连接
        Connection connection = getConnection(statementLog);
        // 获取会话
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 设置参数
        handler.parameterize(stmt);
        return stmt;
    }
    
    // 获取连接
    protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
            return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
            return connection;
        }
    }
    

    一开始就是获取数据库连接,然后执行BaseStatementHandler类(PreparedStatementHandler父类)中的prepare方法,这个方法的作用就是根据Connection创建Statement(或PreparedStatement)对象,也就是上面JDBC操作中的第三步。

    // BaseStatementHandler类(PreparedStatementHandler父类)
    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);
        }
    }
    

    MyBatis默认StatementHandler接口实现类是PreparedStatementHandler,所以MyBatis默认就可以防止SQL注入攻击。我们进入PreparedStatement类的instantiateStatement方法,里面均是调用的原生JDBC方法。先获取到SQL语句,然后通过Connection对象调用prepareStatement方法来获取PreparedStatement对象。

    // 实例化Statement接口
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
            String[] keyColumnNames = mappedStatement.getKeyColumns();
            // 调用原生JDBC的prepareStatement方法生成PreparedStatement对象
            if (keyColumnNames == null) {
                return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
            } else {
                return connection.prepareStatement(sql, keyColumnNames);
            }
        } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
            return connection.prepareStatement(sql);
        } else {
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }
    

    最后会调用PreparedStatementHandler类中的parameterize方法,它的作用就是为PreparedStatement对象设置参数。它又调用了DefaultparameterHandler类的setParameters方法,这里通过BoundSql对象获取所有的参数,得到一个ParameterMapping的集合,然后遍历该集合,获取每个参数的类型处理器(即TypeHandler),再根据参数类型的不同调用各自的设置方法,例如Integer类型参数的TypeHandler就是IntegerTypeHandler,因此就是调用原生JDBC的setInt方法来赋值。

    // PreparedStatementHandler类
    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }
    
    // DefaultparameterHandler类
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 通过BoundSql对象获取到所有参数
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            // 遍历所有参数来为PreparedStatemnet对象赋值
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    // 初始化参数
                    Object value;
                    // 参数名称
                    String propertyName = parameterMapping.getProperty();
                    // 为参数赋值
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) { // 当该参数为null时
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { // 当MyBatis含有该参数的类型处理器时
                        value = parameterObject;
                    } else { // 当MyBatis未含有该参数的类型处理器时
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 获取参数的类型处理器,不同的类型处理器对应了不同的参数设置方法
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    // 获取所指定的JdbcType
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    // 如果要设置null值但是JdbcType又未指定,则设置为默认的Other
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    
                    // 开始为PreparedStatement对象设置参数
                    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);
                    }
                }
            }
        }
    }
    

    注意一下,每个参数的JdbcType对象被传入了setParameter方法,我们进入到该方法。这里就能看见两个非常经典的异常信息,比如当我们为Oracle数据库设置null值但是又没指定JdbcType时就会抛出该异常。而且JdbcType这个参数只当参数为null时才有用,JdbcType如果没有指定,默认值是“Other”。

    // BaseTypeHandler类
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }
            try {
                ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException e) {
                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                                        + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
                                        + "Cause: " + e, e);
            }
        } else {
            try {
                // 调用具体实现类的方法
                setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception e) {
                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
                                        + "Try setting a different JdbcType for this parameter or a different configuration property. "
                                        + "Cause: " + e, e);
            }
        }
    }
    
    // IntegerTypeHandler类
    public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
        throws SQLException {
        // 可以看出如果是设置非空参数,根本没有用到JdbcType
        ps.setInt(i, parameter);
    }
    

    到这里,我们已经获取了数据库连接,又获得了会话对象,参数也设置好了,然后就该执行该SQL了,我们再返回到开头的doQuery方法。

    进入到PreparedStatementHandler类的query方法中,看到其实MyBatis还是通过原生JDBC Statement(或PreparedStatement、CalledStatement)来操作数据库的,这里的步骤就很熟悉了,execute方法执行PreparedStatement预编译的SQL语句。最后交由Resulthandler的实现类DefaultResultSetHandler来处理结果集,并返回一个存有结果集的List泛型集合。

    // PreparedStatementHandler类
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // 执行查询SQL语句
        ps.execute();
        // 处理返回的结果集
        return resultSetHandler.handleResultSets(ps);
    }
    
    // DefaultResultSetHandler类
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        while (rsw != null && resultMapCount > resultSetCount) {
            ResultMap resultMap = resultMaps.get(resultSetCount);
            handleResultSet(rsw, resultMap, multipleResults, null);
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {
            while (rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                    handleResultSet(rsw, resultMap, null, parentMapping);
                }
                rsw = getNextResultSet(stmt);
                cleanUpAfterHandlingResultSet();
                resultSetCount++;
            }
        }
    
        return collapseSingleResultList(multipleResults);
    }
    

    这时候已经把查询出来的一条数据放在缓存中了,再次调用第二条查询语句的话,就不会操作数据库了,而是直接从缓存中拿这条数据。

    至此,MyBatis执行一条SQL的大部分步骤已经全部讲解完毕!

    关于其他添加、修改和删除的执行步骤都大同小异,这三种都是调用的SimpleExecutor类中的doUpdate方法(即都看作是对数据库的修改操作)。

    public int delete(String statement, Object parameter) {
        return update(statement, parameter);
    }
    
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }
    
    public int update(String statement, Object parameter) {
        try {
            dirty = true;
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    
  • 相关阅读:
    团队作业3--需求改进&系统设计
    团队作业2-《帮帮-需求规格说明书》
    团队作业1——团队展示&选题
    动态规划计算文本相似度项目—第一次个人编程作业(软件工程)
    Self Introduction&5 Questions
    团队作业6——复审与事后分析
    事后诸葛亮
    Alpha阶段项目复审
    团队作业1—团队展示&选题
    自我介绍+软工五问
  • 原文地址:https://www.cnblogs.com/SunnyGao/p/14136767.html
Copyright © 2011-2022 走看看