11_Mybatis 是如何进行DO类和数据库字段的映射的?

发布于:2025-08-09 ⋅ 阅读:(23) ⋅ 点赞:(0)

11_Mybatis 是如何进行DO类和数据库字段的映射的?

假设 VideoAbnormalContentMapper.xml 文件有如下方法:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.springboot.videotask.domain.mapper.VideoAbnormalContentMapper">

    <resultMap id="BaseResultMap" type="com.xxx.springboot.videotask.domain.dataobject.VideoAbnormalContentDO">
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="file_id" jdbcType="BIGINT" property="fileId"/>
        <result column="video_name" jdbcType="VARCHAR" property="videoName"/>
        <result column="video_type" jdbcType="VARCHAR" property="videoType"/>
        <result column="model_type" jdbcType="VARCHAR" property="modelType"/>
        <result column="task_status" jdbcType="INTEGER" property="taskStatus"/>
        <result column="task_result" jdbcType="VARCHAR" property="taskResult"/>
        <result column="is_breach" jdbcType="TINYINT" property="isBreach"/>
        <result column="task_result_description" jdbcType="VARCHAR" property="taskResultDescription"/>
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
    </resultMap>

    <select id="selectPageList" resultMap="BaseResultMap" parameterType="map">
        select *
        from video_abnormal_content
        order by id desc limit #{offset}, #{limit}
    </select>

</mapper>

VideoAbnormalContentMapper.java 有如下方法:

List<VideoAbnormalContentDO> selectPageList(@Param("offset") long offset,
                                                    @Param("limit") long limit);

VideoAbnormalContentDO 类如下:

public class VideoAbnormalContentDO extends BaseDO{

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private Long fileId;

    private String videoName;

    private String videoType;

    private String modelType;

    private Integer taskStatus;

    @Schema(description = "是否违规")
    private Boolean isBreach;

    private String taskResult;

    private String taskResultDescription;

}

数据库表如下:

字段名 类型 是否可空 默认值 注释
id bigint NO AUTO_INCREMENT 主键ID
file_id bigint NO 文件id
video_name varchar(255) NO 视频名
video_type varchar(64) NO 视频类型
model_type varchar(255) NO 模型类型
task_status tinyint(1) NO 0 任务状态(0-初始化,1-上传成功,2-上传失败,3-排队中,4-处理中,5-处理完成,6-处理失败,7-已过期,8-已取消)
task_result varchar(255) NO 审核结果
is_breach tinyint(1) NO 0 是否违规
task_result_description varchar(255) NO 结果描述
create_time datetime NO CURRENT_TIMESTAMP 创建时间
update_time datetime NO CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 更新时间

当我们调用

List<VideoAbnormalContent> list = videoAbnormalContentMapper.selectPageList(offset, limit)时,

MyBatis 会生成一个代理对象,最终会调用到 MapperMethod.execute()。(无论是 Mybatis 自带方法,还是我们手写的方法,Mybatis都会生成代理对象)

这里会根据 SQL 类型(SELECT/INSERT/UPDATE/DELETE)调用对应的 SqlSession 方法:

public class MapperMethod {

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch (this.command.getType()) {
            case INSERT:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                // 如果 Mapper 方法返回类型是 void,且带有 ResultHandler,执行带结果处理器的查询
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } 
                // 如果方法返回多个结果,比如返回 List
                else if (this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } 
                // 如果方法返回 Map 类型,比如 @MapKey 注解标记的结果
                else if (this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } 
                // 如果方法返回 Cursor 类型,支持游标逐条遍历
                else if (this.method.returnsCursor()) {
                    result = this.executeForCursor(sqlSession, args);
                } 
                // 默认情况,返回单条记录
                else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                    // 如果方法返回 Optional,包装结果
                    if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + "' attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

显然,这里会调用

// 如果方法返回多个结果,比如返回 List
else if (this.method.returnsMany()) {
    result = this.executeForMany(sqlSession, args);
} 

然后进入 MapperMethodexecuteForMany 方法:

public class MapperMethod {
	private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        // 将方法参数数组转换为 SQL 执行参数对象,可能是单个参数或多个参数封装的对象
        Object param = this.method.convertArgsToSqlCommandParam(args);

        List result;

        // 判断 Mapper 方法是否声明了分页或偏移信息(RowBounds)
        if (this.method.hasRowBounds()) {
            // 从参数中提取 RowBounds 对象(offset + limit)
            RowBounds rowBounds = this.method.extractRowBounds(args);
            // 使用带 RowBounds 的 selectList 执行分页查询
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            // 普通查询,不带分页,直接调用 selectList
            result = sqlSession.selectList(this.command.getName(), param);
        }

        // 判断查询结果 List 是否能直接赋值给 Mapper 方法的返回类型
        // 例如:方法返回 List,查询结果是 ArrayList,直接返回即可
        if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
            // 如果方法返回的是数组,则转换 List 为数组
            return this.method.getReturnType().isArray()
                ? this.convertToArray(result)
                // 否则将 List 转为方法声明的具体集合类型(比如 Set、LinkedList)
                : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        } else {
            // 如果类型兼容,直接返回 List 结果
            return result;
        }
    }
}

这里会执行

else {
    // 普通查询,不带分页,直接调用 selectList
    result = sqlSession.selectList(this.command.getName(), param);
}

接着进入 DefaultSqlSessionselectList 方法:

public class DefaultSqlSession implements SqlSession {

    /**
     * 执行查询,返回多条记录,默认不分页(RowBounds.DEFAULT)
     * @param statement Mapper 映射语句的唯一标识(namespace.id)
     * @param parameter 查询参数,可以是单个对象或 Map
     * @param <E> 返回结果类型
     * @return 查询结果列表
     */
    public <E> List<E> selectList(String statement, Object parameter) {
        // 调用带 RowBounds 参数的重载方法,使用默认分页参数
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }

    /**
     * 执行查询,返回多条记录,可以指定分页参数
     * @param statement Mapper 映射语句的唯一标识
     * @param parameter 查询参数
     * @param rowBounds 分页参数,封装 offset 和 limit
     * @param <E> 返回结果类型
     * @return 查询结果列表
     */
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        // 调用带结果处理器的私有方法,默认不使用结果处理器
        return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
    }

    /**
     * 真正执行查询的方法
     * @param statement Mapper 映射语句的唯一标识
     * @param parameter 查询参数
     * @param rowBounds 分页参数
     * @param handler 结果处理器,一般为 null 或 NO_RESULT_HANDLER
     * @param <E> 返回结果类型
     * @return 查询结果列表
     */
    private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        List<E> result;
        try {
            // 根据 statement 获取映射的 MappedStatement 对象,封装了 SQL、参数、映射等信息
            MappedStatement ms = this.configuration.getMappedStatement(statement);

            // 如果是“脏查询”,设置 dirty 标记(针对缓存相关)
            this.dirty |= ms.isDirtySelect();

            // 调用底层 Executor 执行查询
            // wrapCollection 用于处理参数为集合或数组的情况
            result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);

        } catch (Exception e) {
            // 捕获异常,封装并抛出 MyBatis 异常
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            // 清理错误上下文,避免影响后续操作
            ErrorContext.instance().reset();
        }

        return result;
    }
}

这里会依次调用至

var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);

接着进入 BaseExecutorquery 方法:

public abstract class BaseExecutor implements Executor {

    /**
     * 执行查询,外层入口方法
     * @param ms MappedStatement,封装了SQL、参数映射等信息
     * @param parameter 查询参数对象
     * @param rowBounds 分页参数(offset + limit)
     * @param resultHandler 结果处理器,通常为null
     * @param <E> 返回结果类型
     * @return 查询结果列表
     * @throws SQLException SQL异常
     */
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取绑定的SQL语句和参数映射信息
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 创建缓存键,基于MappedStatement、参数、分页、SQL语句
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        // 传递缓存键和BoundSql进入真正的查询执行方法
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    /**
     * 真正执行查询的方法(带缓存和异常处理)
     * @param ms MappedStatement
     * @param parameter 查询参数
     * @param rowBounds 分页参数
     * @param resultHandler 结果处理器
     * @param key 缓存键
     * @param boundSql 绑定的SQL信息
     * @param <E> 返回结果类型
     * @return 查询结果列表
     * @throws SQLException SQL异常
     */
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 记录错误上下文信息,方便异常时定位资源和SQL id
        ErrorContext.instance()
            .resource(ms.getResource())
            .activity("executing a query")
            .object(ms.getId());

        // 检查执行器是否已经关闭,防止非法操作
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            // 如果是顶层调用且需要刷新缓存,清除本地缓存
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List<E> list;

            try {
                // 查询栈加1,防止递归时重复清理缓存
                ++this.queryStack;

                // 从本地缓存中查找缓存的查询结果(如果没有结果处理器)
                list = resultHandler == null ? (List<E>) this.localCache.getObject(key) : null;

                // 如果缓存命中,处理输出参数(如存储过程的OUT参数)
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } 
                // 缓存未命中,从数据库查询
                else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                // 查询栈减1
                --this.queryStack;
            }

            // 如果是最外层查询调用,执行延迟加载的属性加载,清空延迟加载队列
            if (this.queryStack == 0) {
                Iterator<DeferredLoad> iterator = this.deferredLoads.iterator();
                while (iterator.hasNext()) {
                    DeferredLoad deferredLoad = iterator.next();
                    deferredLoad.load();
                }
                this.deferredLoads.clear();

                // 如果本地缓存作用域是 STATEMENT,执行完毕后清除本地缓存
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            // 返回查询结果
            return list;
        }
    }
}

这里会依次调用至

// 从本地缓存中查找缓存的查询结果(如果没有结果处理器)
list = resultHandler == null ? (List<E>) this.localCache.getObject(key) : null;

// 如果缓存命中,处理输出参数(如存储过程的OUT参数)
if (list != null) {
    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} 
// 缓存未命中,从数据库查询
else {
    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

先判断缓存中有没有,如果没有会调用:

// 缓存未命中,从数据库查询
else {
    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

接着进入 BaseExecutorqueryFromDatabase 方法:

public abstract class BaseExecutor implements Executor {

    /**
     * 从数据库执行查询操作
     * @param ms MappedStatement,包含SQL及映射信息
     * @param parameter 查询参数对象
     * @param rowBounds 分页参数
     * @param resultHandler 结果处理器,通常为null
     * @param key 缓存键
     * @param boundSql 绑定的SQL信息
     * @param <E> 返回结果类型
     * @return 查询结果列表
     * @throws SQLException SQL异常
     */
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
                                          ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 在本地缓存中先放入一个执行占位符,防止循环引用或递归查询时重复执行
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List<E> list;
        try {
            // 执行真正的数据库查询(由子类实现),返回结果列表
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 查询结束后移除占位符,避免缓存污染
            this.localCache.removeObject(key);
        }

        // 查询结果放入本地缓存,供后续相同查询复用
        this.localCache.putObject(key, list);

        // 如果是存储过程调用,缓存输出参数
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        // 返回查询结果
        return list;
    }
}

然后调用:

list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

接着进入 SimpleExecutordoQuery 方法:

public class SimpleExecutor extends BaseExecutor {

    /**
     * 通过 StatementHandler 执行数据库查询,获取结果列表
     * @param ms MappedStatement,封装SQL及映射信息
     * @param parameter 查询参数
     * @param rowBounds 分页参数
     * @param resultHandler 结果处理器,通常为 null
     * @param boundSql 绑定的SQL信息
     * @param <E> 返回结果类型
     * @return 查询结果列表
     * @throws SQLException SQL 异常
     */
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
                               ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        List<E> resultList;

        try {
            // 获取配置对象
            Configuration configuration = ms.getConfiguration();

            // 创建 StatementHandler(核心执行器,负责准备、执行SQL,封装参数和结果映射)
            StatementHandler handler = configuration.newStatementHandler(
                this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

            // 准备 JDBC Statement 对象(包括设置参数、执行前准备等)
            stmt = this.prepareStatement(handler, ms.getStatementLog());

            // 执行查询,交给 StatementHandler 处理,并返回结果列表
            resultList = handler.query(stmt, resultHandler);
        } finally {
            // 关闭 Statement,释放资源
            this.closeStatement(stmt);
        }

        // 返回查询结果
        return resultList;
    }
}

然后调用:

var9 = handler.query(stmt, resultHandler);

接着进入 PreparedStatementHandlerquery 方法:

public class PreparedStatementHandler extends BaseStatementHandler {

    /**
     * 使用 PreparedStatement 执行查询
     * @param statement JDBC Statement 对象,实际是 PreparedStatement
     * @param resultHandler 自定义结果处理器,通常为 null
     * @param <E> 返回结果类型
     * @return 查询结果列表
     * @throws SQLException SQL 执行异常
     */
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        // 强制类型转换为 PreparedStatement,方便调用其特有方法
        PreparedStatement ps = (PreparedStatement) statement;

        // 执行 SQL 查询,返回结果集
        ps.execute();

        // 使用 ResultSetHandler 处理查询结果,转换成结果对象列表并返回
        return this.resultSetHandler.handleResultSets(ps);
    }
}

调用:

return this.resultSetHandler.handleResultSets(ps);

接着进入 DefaultResultSetHandlerhandleResultSets 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 处理 JDBC Statement 返回的所有结果集,映射成 Java 对象列表。
     * @param stmt 执行后的 JDBC Statement
     * @return 多个结果集的映射结果列表,通常只有一个结果集则返回该集合
     * @throws SQLException SQL 异常
     */
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        // 设置错误上下文信息,方便定位问题
        ErrorContext.instance()
            .activity("handling results")
            .object(this.mappedStatement.getId());

        // 存储所有结果集的映射结果
        List<Object> multipleResults = new ArrayList<>();

        int resultSetCount = 0; // 当前处理的结果集序号

        // 获取第一个结果集的封装对象(ResultSetWrapper)
        ResultSetWrapper rsw = this.getFirstResultSet(stmt);

        // 获取当前 MappedStatement 定义的所有 ResultMap(可能有多个结果集对应多个 ResultMap)
        List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();

        int resultMapCount = resultMaps.size(); // ResultMap 数量

        // 校验结果集数量与 ResultMap 数量是否匹配,不匹配会抛异常
        this.validateResultMapsCount(rsw, resultMapCount);

        // 遍历每个结果集,逐个映射成对象
        while (rsw != null && resultMapCount > resultSetCount) {
            // 取对应结果集的 ResultMap 映射配置
            ResultMap resultMap = resultMaps.get(resultSetCount);

            // 处理当前结果集,映射数据行为对象,结果加入 multipleResults
            this.handleResultSet(rsw, resultMap, multipleResults, null);

            // 获取下一个结果集(有些 SQL 语句可能返回多个结果集)
            rsw = this.getNextResultSet(stmt);

            // 清理本次结果集处理时的临时状态
            this.cleanUpAfterHandlingResultSet();

            resultSetCount++;
        }

        // 如果定义了命名的结果集(nestedResultMaps 支持),需要额外处理嵌套结果集
        String[] resultSets = this.mappedStatement.getResultSets();

        if (resultSets != null) {
            while (rsw != null && resultSetCount < resultSets.length) {
                // 获取当前结果集对应的父级映射关系
                ResultMapping parentMapping = this.nextResultMaps.get(resultSets[resultSetCount]);

                if (parentMapping != null) {
                    // 根据嵌套 ResultMap ID 获取嵌套映射配置
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);

                    // 处理嵌套结果集
                    this.handleResultSet(rsw, resultMap, null, parentMapping);
                }

                // 继续获取下一个结果集
                rsw = this.getNextResultSet(stmt);

                // 清理处理状态
                this.cleanUpAfterHandlingResultSet();

                resultSetCount++;
            }
        }

        // 如果只有一个结果集,则返回该结果集的对象列表,否则返回多结果集的集合
        return this.collapseSingleResultList(multipleResults);
    }
}

这里调用:

this.handleResultSet(rsw, resultMap, (List)null, parentMapping);

接着进入 DefaultResultSetHandlerhandleResultSet 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 处理单个 ResultSet 的结果映射
     * 
     * @param rsw 封装了 ResultSet 和元信息的包装类
     * @param resultMap 映射规则,定义了如何将列映射到对象属性
     * @param multipleResults 多结果集合,通常用于处理多结果集的情况
     * @param parentMapping 父级映射,用于嵌套结果映射,平时为 null
     * @throws SQLException SQL 异常
     */
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
        try {
            if (parentMapping != null) {
                // 1. 有父映射时,走嵌套结果映射逻辑
                this.handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
            } else if (this.resultHandler == null) {
                // 2. resultHandler 为 null,使用默认的 DefaultResultHandler 收集结果
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
                this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, null);
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                // 3. 有外部传入的自定义 resultHandler,则用它处理结果
                this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, null);
            }
        } finally {
            // 关闭当前 ResultSet,释放数据库资源
            this.closeResultSet(rsw.getResultSet());
        }
    }
}

调用:

else if (this.resultHandler == null) {
    // 2. resultHandler 为 null,使用默认的 DefaultResultHandler 收集结果
    DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
    this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, null);
    multipleResults.add(defaultResultHandler.getResultList());
}

接着进入 DefaultResultSetHandlerhandleRowValues 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 处理 ResultSet 中的多行数据,将其映射成 Java 对象集合
     *
     * @param rsw           ResultSet 的封装类,包含了 ResultSet 及其元信息
     * @param resultMap     定义映射规则的 ResultMap,说明如何映射数据库列到对象属性
     * @param resultHandler 结果处理器,用于收集或处理映射后的对象,通常为 DefaultResultHandler
     * @param rowBounds     分页参数,控制跳过多少行及最大行数
     * @param parentMapping 父级 ResultMapping,用于多级嵌套映射时传递关联关系,普通查询一般为 null
     * @throws SQLException SQL 异常
     */
    public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        if (resultMap.hasNestedResultMaps()) {
            // 如果 ResultMap 包含嵌套映射(比如一对多、嵌套对象等)
            // 不支持 RowBounds 分页,需抛异常或者跳过分页限制
            this.ensureNoRowBounds();

            // 检查自定义结果处理器是否支持嵌套映射
            this.checkResultHandler();

            // 进入嵌套结果集处理逻辑,递归解析嵌套的结果映射
            this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        } else {
            // 简单映射,即普通的单表字段映射,逐行将结果集映射成对象
            this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        }
    }
}

调用:

else {
    // 简单映射,即普通的单表字段映射,逐行将结果集映射成对象
    this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}

接着进入 DefaultResultSetHandlerhandleRowValuesForSimpleResultMap 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 处理简单的 ResultMap 映射,即不包含嵌套映射的单表映射。
     * 逐行从 ResultSet 中读取数据,将每一行映射成对应的 Java 对象,
     * 并通过 ResultHandler 进行结果收集或处理。
     *
     * @param rsw           ResultSet 的包装类,包含了原始 ResultSet 和元数据信息
     * @param resultMap     映射规则,定义了列与对象属性的对应关系
     * @param resultHandler 结果处理器,负责收集映射后的结果对象
     * @param rowBounds     分页参数,控制跳过的行数和最大处理的行数
     * @param parentMapping 父级 ResultMapping,用于多级嵌套映射时传递上下文,简单映射时为 null
     * @throws SQLException SQL 异常
     */
    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
                                                  RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        // 用于保存处理状态的上下文对象,记录当前处理到第几条记录等信息
        DefaultResultContext<Object> resultContext = new DefaultResultContext<>();

        // 获取底层 JDBC ResultSet
        ResultSet resultSet = rsw.getResultSet();

        // 跳过指定数量的行,支持分页功能
        this.skipRows(resultSet, rowBounds);

        // 遍历 ResultSet,逐行处理映射
        while (this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
            // 根据当前行判断是否使用某个子 ResultMap(支持 discriminator 判别器)
            ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, null);

            // 映射当前行数据为对应的 Java 对象
            Object rowValue = this.getRowValue(rsw, discriminatedResultMap, null);

            // 将映射得到的对象交给 ResultHandler 处理(通常是收集到结果列表中)
            this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
    }
}

调用:

Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null);

接着进入 DefaultResultSetHandlergetRowValue 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 将 ResultSet 中当前行的数据映射成对应的 Java 对象实例。
     * 具体流程:
     * 1. 先通过 createResultObject 创建目标对象(可能通过构造函数或无参构造创建)
     * 2. 判断是否有类型处理器直接处理该对象类型
     * 3. 如果没有类型处理器,则通过 MetaObject 反射对象属性,进行属性赋值映射
     * 4. 支持自动映射未显式配置的列(applyAutomaticMappings)
     * 5. 映射配置中显式的属性(applyPropertyMappings)
     * 6. 支持延迟加载(lazyLoader),如果有延迟加载属性,会保存加载器信息
     * 7. 如果整行数据都为空且配置不允许空实例返回,则返回 null
     *
     * @param rsw          ResultSet 的包装类,包含元信息和原始 ResultSet
     * @param resultMap    映射规则,列到属性的映射
     * @param columnPrefix 列名前缀,用于处理嵌套结果集的列名映射,普通映射传 null
     * @return 映射后的 Java 对象实例,或 null(当数据为空且配置不返回空实例时)
     * @throws SQLException SQL 异常
     */
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        // 用于延迟加载的映射存储
        ResultLoaderMap lazyLoader = new ResultLoaderMap();

        // 创建目标对象实例(调用构造函数或无参构造)
        Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

        // 如果对象创建成功,且该对象类型没有对应的 TypeHandler(简单类型处理器)
        if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 创建 MetaObject,方便反射操作对象属性
            MetaObject metaObject = this.configuration.newMetaObject(rowValue);

            // 标记是否找到有效的列数据
            boolean foundValues = this.useConstructorMappings;

            // 是否开启自动映射功能(自动映射数据库列和对象属性)
            if (this.shouldApplyAutomaticMappings(resultMap, false)) {
                // 自动映射未明确配置的列,返回是否找到数据
                foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
            }

            // 映射所有显式配置的属性映射(对应<result>等标签)
            foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;

            // 如果有延迟加载属性,则视为找到数据
            foundValues = lazyLoader.size() > 0 || foundValues;

            // 如果未找到任何有效数据,且配置不允许返回空对象,则返回 null
            rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
        }

        return rowValue;
    }
}

调用:

Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

接着进入 DefaultResultSetHandlercreateResultObject 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 创建结果对象(映射结果行对应的 Java 实例)
     *
     * 1. 调用重载的 createResultObject 方法,尝试通过构造函数或无参构造创建对象
     * 2. 如果对象创建成功且该类型没有对应的 TypeHandler(即不是简单类型)
     *    - 遍历映射的属性映射列表
     *    - 检查是否存在延迟加载的嵌套查询属性(nestedQueryId 不为空且懒加载开启)
     *    - 如果存在延迟加载,则用代理方式包装该对象,支持延迟加载
     * 3. 标记是否使用了构造函数注入(useConstructorMappings)
     * 4. 返回创建好的对象
     *
     * @param rsw           ResultSet 包装对象,包含元数据和结果集
     * @param resultMap     映射规则,定义如何映射列到对象属性
     * @param lazyLoader    延迟加载的映射集合,用于支持延迟加载属性
     * @param columnPrefix  列名前缀,处理嵌套映射时使用,普通映射传 null
     * @return 映射后的结果对象实例,可能是代理对象
     * @throws SQLException SQL 异常
     */
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        // 初始化构造函数参数类型列表和参数值列表,用于后续构造函数注入
        this.useConstructorMappings = false;
        List<Class<?>> constructorArgTypes = new ArrayList<>();
        List<Object> constructorArgs = new ArrayList<>();

        // 调用重载方法实际创建对象(通过构造函数或无参构造)
        Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);

        // 如果对象创建成功且不是简单类型
        if (resultObject != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 获取所有属性的映射信息
            List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();

            // 遍历所有属性映射,查找是否存在延迟加载的嵌套查询
            for (ResultMapping propertyMapping : propertyMappings) {
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                    // 生成代理对象支持延迟加载,代理会拦截对懒加载属性的访问
                    resultObject = this.configuration.getProxyFactory().createProxy(
                        resultObject, lazyLoader, this.configuration, this.objectFactory,
                        constructorArgTypes, constructorArgs);
                    break; // 找到一个即可,停止遍历
                }
            }
        }

        // 标记是否启用了构造函数注入(构造函数参数列表不为空即为true)
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();

        // 返回创建好的对象实例(可能是代理对象)
        return resultObject;
    }
}

这里会调用:

Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);

接着进入 DefaultResultSetHandlercreateResultObject 方法:

public class DefaultResultSetHandler implements ResultSetHandler {

    /**
     * 创建结果对象实例
     *
     * 根据 resultMap 中的映射规则和目标类型,创建对象实例,具体逻辑如下:
     *
     * 1. 如果目标类型有对应的 TypeHandler(简单类型,如基本类型、包装类、String 等)
     *    - 调用 createPrimitiveResultObject,直接从结果集取对应列映射成简单类型返回
     *
     * 2. 如果 resultMap 定义了构造函数映射(constructorMappings 非空)
     *    - 调用 createParameterizedResultObject,通过带参数构造函数创建对象
     *
     * 3. 如果目标类型不是接口,且没有无参构造函数
     *    - 如果允许自动映射,则尝试根据构造函数参数名称和类型自动匹配列值,调用 createByConstructorSignature 创建对象
     *    - 否则抛出异常,无法实例化
     *
     * 4. 其他情况(存在无参构造函数或接口类型)
     *    - 通过 objectFactory 调用无参构造函数创建实例
     *
     * @param rsw               ResultSet 封装类,包含当前结果集和元数据信息
     * @param resultMap         映射规则,定义如何映射数据库列到对象属性
     * @param constructorArgTypes   输出参数,记录构造函数参数类型,用于后续映射
     * @param constructorArgs       输出参数,记录构造函数参数值
     * @param columnPrefix       列名前缀,用于嵌套结果映射的列过滤,普通映射时为 null
     * @return 创建好的对象实例,或基本类型映射值
     * @throws SQLException SQL 异常
     */
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap,
                                      List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
                                      String columnPrefix) throws SQLException {
        // 目标映射类型
        Class<?> resultType = resultMap.getType();
        // 元信息工具,用于获取类的构造函数、方法等反射信息
        MetaClass metaType = MetaClass.forClass(resultType, this.reflectorFactory);
        // 构造函数映射列表(通过 <constructor> 标签定义的映射)
        List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();

        // 1. 如果类型有对应的 TypeHandler,则直接调用 createPrimitiveResultObject 创建简单类型对象
        if (this.hasTypeHandlerForResultObject(rsw, resultType)) {
            return this.createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        }
        // 2. 有构造函数映射,则调用带参构造函数创建对象
        else if (!constructorMappings.isEmpty()) {
            return this.createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
        }
        // 3. 没有无参构造函数,且不是接口,尝试自动根据构造函数参数映射创建对象
        else if (!resultType.isInterface() && !metaType.hasDefaultConstructor()) {
            if (this.shouldApplyAutomaticMappings(resultMap, false)) {
                return this.createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs);
            } else {
                // 无法创建实例,抛异常
                throw new ExecutorException("Do not know how to create an instance of " + resultType);
            }
        }
        // 4. 其他情况,调用无参构造函数创建实例
        else {
            return this.objectFactory.create(resultType);
        }
    }
}

注意:

createResultObject 可能存在的几种情况及其处理方式

情况序号 条件描述 处理方式 备注
1 目标类型有对应的 TypeHandler 调用 createPrimitiveResultObject,直接从结果集取对应列映射成简单类型返回 例如:IntegerStringLongDate等简单类型
2 resultMap 中定义了构造函数映射(constructorMappings 非空) 调用 createParameterizedResultObject,通过带参数构造函数创建对象 构造函数参数和数据库列显式绑定,通常通过 XML <constructor> 标签配置
3 目标类型不是接口,且没有无参构造函数 如果允许自动映射,则调用 createByConstructorSignature,根据构造函数参数自动匹配列值创建对象 自动匹配可以基于参数名称或参数顺序匹配数据库列(需要 JDK 8+ 参数名支持)
4 目标类型不是接口,且没有无参构造函数,且不允许自动映射 抛出异常 ExecutorException,提示无法实例化 表示开发者需要补充无参构造函数或者配置构造函数映射
5 目标类型是接口或者有无参构造函数 调用 objectFactory.create(resultType),通过无参构造函数创建实例 最常用情况,MyBatis 创建实例后通过 setter 或反射设置属性

其实,逻辑走到这里就一目了然了,mybatis 映射优先级从上至下依次排列,而且需要注意的是,如果是通过有参构造器进行映射,则会按照构造器参数顺序依次映射,如果结果集顺序,和有参构造器的参数顺序不一致,会导致映射失败。


网站公告

今日签到

点亮在社区的每一天
去签到