建行2面:Mybatis是如何工作的?

嗨,你好呀,我是猿java

MyBatis 是一款优秀的持久层框架,它通过简化 JDBC操作和提供灵活的 SQL映射方式,使 Java 开发人员能够更高效地进行数据库操作。那么,MyBatis的执行原理是什么?这篇文章我们将深入地分析。

1. MyBatis 配置解析

MyBatis 的配置文件通常包括全局配置文件(mybatis-config.xml)和映射文件(XXXMapper.xml)。全局配置文件主要用于配置数据源和其他全局性的信息,而映射文件则用于定义 SQL 语句。

1.1 全局配置文件解析

全局配置文件在 MyBatis 启动时被解析。SqlSessionFactoryBuilder 是 MyBatis 解析配置文件的入口点。它通过 build 方法接收一个 Reader 或 InputStream,然后调用 XMLConfigBuilder 来解析 XML 配置文件。

java 复制代码
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

XMLConfigBuilder 解析配置文件并构建出 Configuration 对象,该对象包含了 MyBatis 的所有配置信息。

1.2 映射文件解析

映射文件中定义了 SQL 语句,通过 XMLMapperBuilder 进行解析。每个 <mapper> 标签对应一个 MappedStatement 对象,MappedStatement 包含了 SQL 语句、输入输出参数类型、结果集映射等信息。

java 复制代码
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

2. SQL 语句解析

MyBatis 支持动态 SQL,通过 <if>, <choose>, <foreach> 等标签,可以根据不同的条件构造 SQL。动态 SQL 是 MyBatis 的一大特色,通过 SqlSource 接口实现。SqlSource 的主要实现类有 StaticSqlSource, DynamicSqlSource, RawSqlSource 等。

2.1 动态 SQL 解析

DynamicSqlSource 是处理动态 SQL 的核心类。它通过 SqlNode 树来表示 SQL 语句的结构,SqlNode 是一个接口,常用的实现类有 IfSqlNode, ChooseSqlNode, WhereSqlNode 等。每个 SqlNodeapply 方法负责将节点转换为 SQL 字符串。

java 复制代码
public class DynamicSqlSource implements SqlSource {
    private final Configuration configuration;
    private final SqlNode rootSqlNode;

    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }
    
    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        return sqlSource.getBoundSql(parameterObject);
    }
}

3. 参数设置

在获得最终的 SQL 语句后,MyBatis 需要将参数传递给 SQL 语句。ParameterHandler 接口负责这项工作,默认实现是 DefaultParameterHandler

java 复制代码
public class DefaultParameterHandler implements ParameterHandler {
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

    public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }

    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                }
            }
        }
    }
}

4. SQL 执行

SQL 执行是 MyBatis 的核心功能之一。Executor 接口定义了执行操作的基本方法,主要的实现类有 SimpleExecutor, ReuseExecutor, BatchExecutor。这些执行器通过 StatementHandler 执行 SQL 语句。

java 复制代码
public class SimpleExecutor extends BaseExecutor {
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }
}

5. 结果处理

MyBatis 提供了强大的结果集映射功能,允许将 SQL 查询结果映射为 Java 对象。ResultSetHandler 接口负责处理结果集,DefaultResultSetHandler 是其主要实现类。

java 复制代码
public class DefaultResultSetHandler implements ResultSetHandler {
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final ObjectFactory objectFactory;
    private final boolean useConstructorMappings;
    private final ReflectorFactory reflectorFactory;
    private final MappedStatement mappedStatement;
    private final RowBounds rowBounds;
    private final ParameterHandler parameterHandler;
    private final ResultHandler<?> resultHandler;
    private final BoundSql boundSql;

    public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.objectFactory = mappedStatement.getConfiguration().getObjectFactory();
        this.useConstructorMappings = mappedStatement.getConfiguration().isUseConstructorMappings();
        this.reflectorFactory = mappedStatement.getConfiguration().getReflectorFactory();
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
        this.parameterHandler = parameterHandler;
        this.resultHandler = resultHandler;
        this.boundSql = boundSql;
    }

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        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++;
        }
        return collapseSingleResultList(multipleResults);
    }
}

6. 总结

本文,我们通过核心源码分析了 MyBatis, 它是一个轻量级的 ORM框架,它通过配置文件和注解将 Java 对象与数据库记录映射起来,其核心在于通过 XML和注解配置 SQL语句,利用执行器执行 SQL,并通过结果集处理器将结果映射为 Java对象。

MyBatis的设计使得开发者可以专注于 SQL本身,而不必关心底层 JDBC操作的细节,了解和掌握其执行原理和设计模式,可以帮组我们在实际应用中更好地使用 MyBatis。

7. 交流学习

最后,把猿哥的座右铭送给你:投资自己才是最大的财富。 如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

相关推荐
坐吃山猪3 分钟前
JDK 8 到 JDK 21 新特性知识要点
java·开发语言·windows
坐吃山猪5 分钟前
【多线程】FutureTask多线程底层实现
java·开发语言·数据库
weixin199701080169 分钟前
[特殊字符]《从0到1:闲鱼开放平台授权登录 + AccessToken 刷新 + 聚石塔部署完整链路》(附Python源码)
java·数据库·python
Freak嵌入式26 分钟前
RP2040 PIO 编程模型与状态机原理:从硬件架构到工作逻辑全解析
java·大数据·开发语言·单片机·嵌入式硬件·硬件架构
kakawzw33 分钟前
Netty源码笔记
java·服务器·后端
zhuodedao36 分钟前
Spring AI + MCP 文件工具未调用问题复盘:为什么初始化成功却没有写入文件?
java·debug·agent·springai·mcp
迪飞特科技1 小时前
《MyBatis 批量插入性能优化的 5 个关键参数配置》
android·性能优化·mybatis
Java小白笔记1 小时前
Java中大数据实时归集与指标汇总方案
java·大数据·开发语言
随遇而安zx1 小时前
【地基篇】---Java 8 JVM 知识大纲
java·开发语言·jvm
雾隐隐o1 小时前
Docker 镜像归档与容器管理
java·docker·eureka