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

相关推荐
《源码好优多》1 小时前
基于Java Springboot出租车管理网站
java·开发语言·spring boot
·云扬·5 小时前
Java IO 与 BIO、NIO、AIO 详解
java·开发语言·笔记·学习·nio·1024程序员节
求积分不加C5 小时前
Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程
java·spring boot·后端
枫叶_v5 小时前
【SpringBoot】26 实体映射工具(MapStruct)
java·spring boot·后端
东方巴黎~Sunsiny5 小时前
java-图算法
java·开发语言·算法
2401_857617626 小时前
汽车资讯新趋势:Spring Boot技术解读
java·spring boot·后端
小林学习编程7 小时前
从零开始理解Spring Security的认证与授权
java·后端·spring
写bug的羊羊7 小时前
Spring Boot整合Nacos启动时 Failed to rename context [nacos] as [xxx]
java·spring boot·后端
努力的小陈^O^8 小时前
docker学习笔记跟常用命令总结
java·笔记·docker·云原生
童先生8 小时前
如何将java项目打包成docker 镜像并且可运行
java·开发语言·docker