Mybatis源码---个人总结

Mybatis操作数据库主要步骤


  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

  User user = userMapper.getUserById(2L);

1.通过动态代理获取Mapper对象

 从debug可以看出,knownMapper已经初始化代码中所有的mapper对象

具体如何初始化数据后续作答

从MapperProxyFactory的实例化方法中可以看出,所有的Mapper对象都是通过MapperProxy<T>代理对象中获取的mapper对象 

说明MapperProxy是通过jdk动态代理实现,既然说MapperProxy是个代理对象,从以上可以看出我们已经获取了Mapper的代理对象

2.获取mapperMethod对象(SqlCommand,Signature)

上文中说到MapperProxy是个代理类,那么代理类所有的方法执行都会经过invoke方法,现在让我们来看看它的invoke方法

MapperMethod mapperMethod = this.cachedMapperMethod(method);

由源码可以看出,这步已经获取了MapperMethod对象,那我们看看源码是怎么获取的

在缓存methodCache获取,若没有,则创建MapperMethod对象,并且添加到缓存中,接下来看看MapperMethod构造有些操作

 我们先来看看sql指令

 

 debug可以看出,调用代理类的方法时,所有经过invoke的方法信息已经携带进来了

MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);

可以看出sqlCommand的构造主要操作都是获取MapperStatement这个对象

 拼接statementId = "mapper.UserMapper.getUserById",接下来看看具体用这个操作了啥

 

 

 

 我们先不管这个Maps定义是啥,debug来看这个目前都是空

 看了这么多,实际上就是判断之前Map<String,MapperStatement>之前是否存在getUserById这个mapper和方法,存在的话之前将Map中的MapperStatement返回

那么问题来了,我们之前也没有往里面put元素,看来mybatis在加载的时候已经做了初始化操作,具体初始化后续再说

当然如果MapperStatement不存在的话,获取该类UserMapper.Class所有的接口中是否存在对应的MapperStatement,不存在直接返回null

当然如果mapperStateMent不存在的话,若该方法上存在@Flush注解,说明该次执行flush操作,

public enum SqlCommandType {
  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}

 从以前我们可以看出sqlCommand主要包含了方法名称和操作数据库方式(select,update,delete,insert,flush)

那么我们再来看看方法签名MethodSignature构造具体有哪些操作

 

 可以看出方法签名的构造主要是为了一堆属性赋值操作,那我们来看看这些属性代表啥

returnType:方法返回值类型

returnsVoid:返回值是否为void类型(方法是否有返回值)

returnsMany:返回值是否是数组或集合

returnsCursor:返回值是否cursor类型

returnsMap:返回值是否是Map类型

rowBoundsIndex:入参为RowBounds的下标号

resultHandleIndex:入参为ResultHandle的下标号

最后生成paramNameResolve实例对象

/**
     * 解析方法入参,维护到names中。
     */
    public ParamNameResolver(Configuration config, Method method) {
        // eg1: paramTypes[0] = Long.class
        final Class<?>[] paramTypes = method.getParameterTypes();

        // eg1: paramAnnotations[0][0] = @org.apache.ibatis.annotations.Param(value=id)
        /**
         * 首先举个例子:
         * @RedisScan
         * public void save(@RedisSave() int id, @RedisSave() String name){
         *  ... ...
         * }
         *
         * Annotation[][] annos = method.getParameterAnnotations();
         * 二维数组中:第一个参数下标为0,第二参数下标为1
         * 即:annos[0][0]=RedisSave 和 annos[1][0]=RedisSave,也就是说,二维数组是包含多个仅有一个值的数组。
         * 因为参数前可以添加多个注解,所以是二维数组;一个参数上不可以添加相同的注解,同一个注解可以加在不同的参数上。
         */
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap<Integer, String> map = new TreeMap<>();
        // eg1: paramCount = 1
        int paramCount = paramAnnotations.length;
        /**
         * get names from @Param annotations
         */
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
            // 判断是否是特殊的参数——即:RowBounds.class或ResultHandler.class
            if (isSpecialParameter(paramTypes[paramIndex])) {
                continue;
            }
            String name = null;
            // eg1: paramAnnotations[0] = @org.apache.ibatis.annotations.Param(value=id)
            /** 使用@Param指定的入参名称 */
            for (Annotation annotation : paramAnnotations[paramIndex]) {
                if (annotation instanceof Param) {
                    hasParamAnnotation = true;
                    // eg1: name = "id"
                    name = ((Param) annotation).value();
                    break;
                }
            }
            /** 没有使用@Param指定的入参名称 */
            if (name == null) {
                /** @Param was not specified;useActualParamName默认值为true*/
                if (config.isUseActualParamName()) {
                    /** use the parameter index as the name ("arg0", "arg1", ...) */
                    name = getActualParamName(method, paramIndex);
                }
                if (name == null) {
                    /** use the parameter index as the name ("0", "1", ...) */
                    name = String.valueOf(map.size());
                }
            }
            // eg1: paramIndex=0  name="id"
            map.put(paramIndex, name);
        }
        // eg1: names={0:"id"}
        names = Collections.unmodifiableSortedMap(map);
    }

让我们看看代码具体是怎么生成paramNamesResolve

主要是判断循环判断入参是否存在@param注解,若存在 map.put("参数下标",@Param注解自定义的value),如不存在@Param注解 map.put("参数下标","参数名称")

 现在来看我我们已经获取mapperMethod对象已经构造完毕了,再看mapperMethod执行的时候具体有哪些操作

3.根据MapperMethod构造跳转执行语句

/**
     * MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中。实际上它最后就是通过SqlSession对象去运行对象的SQL。
     */
    // eg1: sqlSession = DefaultSqlSession@1953  args = {2L}
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // eg1: command.getType() = SELECT
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // eg1: method.returnsVoid() = false  method.hasResultHandler() = false
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) { // eg1: method.returnsMany() = false
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) { // eg1: method.returnsMap() = false
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) { // eg1: method.returnsCursor() = false
                    result = executeForCursor(sqlSession, args);
                } else {
                    // eg1: args = {2L}
                    /** 将参数转换为sql语句需要的入参 */
                    Object param = method.convertArgsToSqlCommandParam(args);

                    // eg1: sqlSession=DefaultSqlSession  command.getName()="mapper.UserMapper.getUserById" param={"id":2L, "param1":2L}
                    /** 执行sql查询操作 */
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                    + " attempted to return null from a method with a primitive return type (" + method.getReturnType()
                    + ").");
        }
        return result;
    }

由以上可以看出,根据我们之前构造的sqlCommand(sql指令)判断操作数据库的类型,我们现在以getUserById(SELECT)为例

param = this.method.convertArgsToSqlCommandParam(args);

然后再根据args参数转为sql需要的参数

param={"id":2L, "param1":2L}

最后再根据返回值类型判断,sqlSession需要执行的方法

以上步骤主要是为了解析自定义的Mapper对象和方法,最终查询数据库操作是由sqlSession来操作

4.sqlSession查询前的缓存操作(selectOne为例)

 可以看出selectOne也是通过selectList获取第一个元素,然而不是一个元素的话,直接抛出异常

 可以看出这个MappedStatement又出现了,之前说过

 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

这个字段在加载的时候mybatis已经初始化了,那么接下来就是调用Executor执行器来执行查询操作

可以看出查询之前会先执行CachingExecutor(缓存),从缓存中查询数据,那么我们来看看这个缓存又做了那些操作

Cache cache = ms.getCache();

 如果在UserMapper.xml配置了<cache/>开启了二级缓存,则cache不为null

判断flushCacheRequired是否刷新二级缓存

判断UserCache是否将本条sql结果进行二级缓存

SELECT:flushCacheRequired默认false,UserCache默认true

UPDATE.Delete,insert:flushCacheRequired默认为true

 由以上代码可以看出,二级缓存的数据是从Cache对象中获取的

但是源码中看,从数据库中查询的数据只会维护到TransactionalCache的本地Map中,根本没有维护Cache缓存,那么这个时候就需要commit作用就来了

只有在commit之后,二级缓存才会真正写入 

接下来如果二级缓存没有开启/没有数据,我们来看看查询数据库有哪些操作呢

1.根据入参和ResultMap构建BoundSql,所以我们之前在log看到的sql语句都是通过getBoundSql方法构造的

// eg1: parameterObject = {"id":2L, "param1":2L}
    public BoundSql getBoundSql(Object parameterObject) {
        // eg1: sqlSource = RawSqlSource  parameterObject = {"id":2L, "param1":2L}
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

        // eg1: parameterMappings[0] = {property='id', mode=IN, javaType=class java.lang.Long, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        // eg1: parameterMappings不为空
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            /** 如果boundSql里的parameterMappings为空,那么用parameterMap的parameterMappings再次构建权限的BoundSql对象 */
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }

        /**
         * 如果配置了resultMap,则判断赋值hasNestedResultMaps,判断是否有聚合的结果集
         */
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            // eg1: rmId = resultMapId = null  由于UserMapper.xml中配置的是resultType="vo.User"而不是resultMap,所以rmId=null
            String rmId = pm.getResultMapId();
            // eg1: rmId = null
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }

        return boundSql;
    }

 那么现在就有人问问啥我写的#{},为啥变成了?方式拼接

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    char[] src = text.toCharArray();
    int offset = 0;
    // search open token
    int start = text.indexOf(openToken, offset);
    if (start == -1) {
      return text;
    }
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }

 具体是怎么拼接的你们可以自己研究研究,这里暂不做说明

// eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
    @SuppressWarnings("unchecked")
    @Override
    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());
        // eg1: closed = false
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }

        // eg1: queryStack = 0  ms.isFlushCacheRequired() = false
        /** 如果配置了flushCacheRequired=true并且queryStack=0(没有正在执行的查询操作),则会执行清空缓存操作*/
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }

        List<E> list;
        try {
            /** 记录正在执行查询操作的任务数*/
            queryStack++; // eg1: queryStack=1

            // eg1: resultHandler=null localCache.getObject(key)=null
            /** localCache维护一级缓存,试图从一级缓存中获取结果数据,如果有数据,则返回结果;如果没有数据,再执行queryFromDatabase */
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // eg1: list = null
            if (list != null) {
                /** 如果是执行存储过程 */
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        if (queryStack == 0) {
            /** 延迟加载处理 */
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();

            // eg1: configuration.getLocalCacheScope()=SESSION
            /** 如果设置了<setting name="localCacheScope" value="STATEMENT"/>,则会每次执行完清空缓存。即:使得一级缓存失效 */
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }

1.如果配置了FlushCacheRequired且没有正在执行的sql语句时,先清空缓存

2.记录正在执行sql标记

3.localCache维护一级缓存,试图从一级缓存中获取结果数据,如果有数据,则返回结果;如果没有数据,再执行queryFromDatabase,将查询的数据再缓存到一级缓存localCache
4.判断该次statementType是否为存储过程 ==>  该章不做讲解
5.延迟加载处理

6.如果设置了<setting name="localCacheScope" value="STATEMENT"/>,则会每次执行完清空缓存。即:使得一级缓存失效

步骤总结:该步骤主要是为了做查询前的缓存操作 CacheExecutor维护二级缓存  localCache维护一级缓存

5.DB操作

// eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
    @Override
    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);

            // eg1: handler=RoutingStatementHandler
            /** 然后使用prepareStatement方法,对SQL进行预编译并设置入参 */
            stmt = prepareStatement(handler, ms.getStatementLog());

            // eg1: handler=RoutingStatementHandler parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
            /** 开始执行真正的查询操作。将包装好的Statement通过StatementHandler来执行,并把结果传递给resultHandler */
            return handler.<E>query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }

1.根据configuration构建PrepareStatementHandler

// eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                                   ResultHandler resultHandler, BoundSql boundSql) {
        /**
         * StatementType: STATEMENT, PREPARED, CALLABLE
         */
        // eg1: ms.getStatementType() = PREPARED
        switch (ms.getStatementType()) {
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                // eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
                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());
        }

    }

从这行代码可以看出我们现在获取的StatementHandler为预编译处理器,那么预编译处理器又是怎么构造的呢

// eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
    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();

        // eg1: boundSql不为空,不执行该段逻辑
        if (boundSql == null) {
            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);
    }

最终只是为从BaseStatementHandler处理器中获取预编译处理器PrepareStatementHandler,说实话这块我看着我觉得没啥用

2.对获取的预编译处理器PrepareStatementHandler进行拦截处理

/**
     * 构建RoutingStatementHandler,并添加到拦截器链interceptorChain中
     */
    // eg1: parameter = {"id": 2L, "param1", 2L}  rowBounds = new RowBounds() resultHandler = null
    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);
        // eg1: interceptorChain中是空Chain,由于interceptorChain中没有执行addInterceptor()来添加Interceptor,所以执行pluginAll这个也是徒劳的。
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        // eg1: statementHandler=PreparedStatementHandler
        return statementHandler;
    }

这段代码主要是为了加载配置文件中自定义的拦截器,目前我们先不考虑拦截器情况,所以这行代码对我们没啥影响

以上代码主要是addInterceptor的方式

3.使用PrepareStatementHandler设置入参

/**
     * 使用prepareStatement方法,对SQL编译并设置入参
     *
     * @param handler
     * @param statementLog
     * @return
     * @throws SQLException
     */
    // eg1: handler=RoutingStatementHandler
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;

        /** 获得Connection实例 */
        Connection connection = getConnection(statementLog);

        // eg1: handler=RoutingStatementHandler
        /** 第一步:调用了StatementHandler的prepared进行了【sql的预编译】 */
        stmt = handler.prepare(connection, transaction.getTimeout());

        /** 第二步:通过PreparedStatementHandler的parameterize来给【sql设置入参】 */
        handler.parameterize(stmt);

        // eg1: 返回org.apache.ibatis.logging.jdbc.PreparedStatementLogger@2e570ded
        return stmt;
    }

 1.先根据connection,准备预编译语句

// eg1: connection
    /**
     * 准备预编译语句
     */
    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        // eg1: sql="select id, name, age from tb_user where id = ?"
        String sql = boundSql.getSql();

        // eg1: mappedStatement.getKeyGenerator=NoKeyGenerator
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
            String[] keyColumnNames = mappedStatement.getKeyColumns();
            if (keyColumnNames == null) {
                return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
            } else {
                return connection.prepareStatement(sql, keyColumnNames);
            }
        }
        // eg1: mappedStatement.getResultSetType() = null
        else if (mappedStatement.getResultSetType() != null) {
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(),
                    ResultSet.CONCUR_READ_ONLY);
        } else {
            // eg1: sql="select id, name, age from tb_user where id = ?"
            /** 准备预编译语句 */
            return connection.prepareStatement(sql);
        }
    }

1.主键生成策略我们目前没有,暂时无需考虑

2.我们设置的select语句返回类型为ResultType,所以也不用考虑

3.调用JDBCconnection准备预编译语句

/** 准备预编译语句 */
return connection.prepareStatement(sql);

从以上来看,我们已经对PrepareStatementHandler设置的入参以及sql语句,接下来就可以用PrepareStatementHandler进行数据操作了

// eg1: delegate = PreparedStatementHandler  resultHandler = null
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        /** 最终还是使用JDBC去进行数据操作 */
        PreparedStatement ps = (PreparedStatement) statement;

        /** 执行查询操作 */
        ps.execute();

        // eg1: 封装结果集 resultSetHandler=DefaultResultSetHandler
        /** 将结果集进行封装 */
        return resultSetHandler.handleResultSets(ps);
    }

以上代码才是PrepareStatementHandler使用JDBC进行数据操作

6.结果集封装

/**
     * 处理数据库操作的结果集
     */
    // eg1: 执行到这里
    @Override
    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 */
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        /** 其次:如果rsw != null && resultMapCount < 1,则抛异常ExecutorException */
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size(); // eg1: resultMapCount = 1
        validateResultMapsCount(rsw, resultMapCount);

        // eg1: rsw不为空 resultMapCount=1 resultSetCount=0
        /** 第三步:处理结果集 */
        while (rsw != null && resultMapCount > resultSetCount) {
            // eg1: ResultMap resultMap=resultMaps.get(0);
            ResultMap resultMap = resultMaps.get(resultSetCount);

            /** 处理结果集, 存储在multipleResults中 */
            handleResultSet(rsw, resultMap, multipleResults, null);

            // eg1: rsw=null
            rsw = getNextResultSet(stmt);

            cleanUpAfterHandlingResultSet();
            resultSetCount++; // eg1: 自增后resultSetCount=1
        }

        String[] resultSets = mappedStatement.getResultSets();
        // eg1: resultSets = null
        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++;
            }
        }

        // eg1: multipleResults.get(0).get(0) = User{id=2, name='muse2', age=24, userContacts=null}
        /** 返回结果 */
        return collapseSingleResultList(multipleResults);
    }

1.通过JDBC通过statement获取resultSet,并将resultSet封装到ResultSetWrapper

2.校验ResultSetWrapper中是否有数据且ResultMap不能小于1

3.处理结果集ResultSetWrapper存储到multipleResults,然后在获取下一个结果集再次循环存储

4.由于我们xml中使用的ResultType返回类型,所以ResultSets情况不需要考虑    ==> ResultMap和ResultType在statement中都是ResultMaps属性

5.最后将结果集返回,至于如果想细致的查看ResultMap和ResultSetWrapper数据怎么转换可以自己跟源码进去看看

总结:

mybatis操作数据除了以上主要六大步骤之外,主要有很多数据都是在mybatis加载的时候从mybatis-config.xml配置文件以及对应的Mapper.xml文件中已经初始化好了

接下来再来看看基本的一些初始化数据

 这是我们一般使用mybatis的时候都需要通过SqlSessionFactory获取Sqlsession代理对象

那么我们在构建SqlSessionFactory的时候,需要将xml文件/property文件/环境变量解析 

/**
     * 解析配置文件
     *
     * @return
     */
    public Configuration parse() {
        // 每个XML配置构造器实例只允许解析配置文件一次
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // 解析config.xml配置文件最重要的方法。选取根节点<configuration>
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
/**
     * 对xml的配置文件解析到Configuration对象中。
     *
     * @param root
     */
    private void parseConfiguration(XNode root) {
        try {
            // 将<properties>的配置转化赋值Configuration的variables属性
            propertiesElement(root.evalNode("properties"));

            // 将<settings>的配置转化赋值Properties,并检查Configuration中是否有对应的属性,如果没有,抛异常,终止整个解析流程。
            Properties settings = settingsAsProperties(root.evalNode("settings"));

            /**
             * 对settings中的vfsImp属性进行二次处理,生成实例对象,并赋值Configuration的vfsImpl属性
             *
             * 为什么解析<settings>中的值为Properties不能都在settingsAsProperties方法中完成,而要单独在loadCustomVfs中完成?
             * 回答;由于vfsImpl配置的是Class,需要通过ClassLoader加载为对象,赋值到Configuration里,而不像其他String类型的value
             * ,可以直接赋值进去并使用。所以单独在loadCustomVfs方法中进行了Configuration中的vfsImpl赋值操作。
             */
            loadCustomVfs(settings);

            // 将<typeAliases>的配置转化赋值Configuration的typeAliasRegistry属性
            typeAliasesElement(root.evalNode("typeAliases"));

            // 解析<plugins>标签,初始化Configuration的interceptorChain
            pluginElement(root.evalNode("plugins"));

            // 解析<objectFactory>标签
            objectFactoryElement(root.evalNode("objectFactory"));

            // 解析<objectWrapperFactory>标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

            // 解析<reflectorFactory>标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));

            // 将配置信息中settings赋值给Configuration
            settingsElement(settings);

            // 解析<environments>标签
            environmentsElement(root.evalNode("environments"));

            // 解析<databaseIdProvider>标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));

            // 解析<databaseIdProvider>标签
            typeHandlerElement(root.evalNode("typeHandlers"));

            // 解析<mappers>标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

以上代码就是解析各个mybatis-config根目录<configuration/>下的各个节点,并且最终赋值到  ==>  具体自己可以查看BaseBuilder以及Configuration的属性,以上就是mybatis初始化数据的基本流程,至于初始化操作的细节步骤,自己可以跟源码看看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值