建行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,持续输出硬核文章。

相关推荐
毕设源码-朱学姐5 小时前
【开题答辩全过程】以 工厂能耗分析平台的设计与实现为例,包含答辩的问题和答案
java·vue.js
Spring AI学习7 小时前
Spring AI深度解析(9/50):可观测性与监控体系实战
java·人工智能·spring
java1234_小锋7 小时前
Spring IoC的实现机制是什么?
java·后端·spring
xqqxqxxq8 小时前
背单词软件技术笔记(V2.0扩展版)
java·笔记·python
消失的旧时光-19438 小时前
深入理解 Java 线程池(二):ThreadPoolExecutor 执行流程 + 运行状态 + ctl 原理全解析
java·开发语言
哈哈老师啊8 小时前
Springboot学生综合测评系统hxtne(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·数据库·spring boot
4311媒体网8 小时前
帝国cms调用文章内容 二开基本操作
java·开发语言·php
GSDjisidi9 小时前
东京IT软件会社-(株)GSD|多种技术栈募集,高度人才+20分
开发语言·面试·职场和发展
zwxu_9 小时前
Nginx NIO对比Java NIO
java·nginx·nio
可观测性用观测云10 小时前
Pyroscope Java 接入最佳实践
java