码迷,mamicode.com
首页 > 其他好文 > 详细

MyBatis详细源码解析(下篇)

时间:2020-12-19 13:13:21      阅读:2      评论:0      收藏:0      [点我收藏+]

标签:遍历   本地缓存   ppi   rem   news   HERE   protected   red   回忆一下   

第四步

这一步的主要目的就是通过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();
    }
}

MyBatis详细源码解析(下篇)

标签:遍历   本地缓存   ppi   rem   news   HERE   protected   red   回忆一下   

原文地址:https://www.cnblogs.com/SunnyGao/p/14136767.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!