一文搞懂Mybatis的工作原理

 更新时间:2025年11月13日 09:30:00   作者:Calvad0s  
MyBatis和Spring的执行过程包括启动阶段和运行阶段,启动阶段定义和解析配置文件,运行阶段读取配置文件并实现功能,执行SQL的逻辑包括代理类的生成、SQL的执行、缓存和查询数据库,本文给大家介绍Mybatis的工作原理,感兴趣的朋友跟随小编一起看看吧

无论是Mybatis也好,Spring也罢,它们的执行过程无非可分为启动阶段和运行阶段:

启动阶段:

  1. 定义配置文件,如XML、注解
  2. 解析配置文件,将配置文件加载到内存当中

运行阶段:

  1. 读取内存中的配置文件,并根据配置文件实现对应的功能

对于执行SQL的逻辑来讲,有如下步骤:

当配置完成之后,假如说我们要执行一个下面一个sql,那么该如何执行呢?

TestMapper testMapper = session.getMapper(TestMapper.class);
Test test = testMapper.findOne(1);

一、代理类的生成

首先 MyBatis 会根据我们传入接口通过 JDK 动态代理,生成一个代理对象 TestMapper,生成逻辑如下所示:

public T newInstance(SqlSession sqlSession) {
    // mapperProxy 实现了 InvocationHandler 接口,用于 JDK 动态代理
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}
// 通过 JDK 动态代理生成对象
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
        new Class[]{ mapperInterface }, mapperProxy);
}

代理类的主要逻辑在 MapperProxy 中,而代理逻辑则是通过 MapperMethod 完成的。

对于 MapperMethod 来说,它在创建的时候是需要读取 XML 或者方法注解的配置项,所以在使用的时候才能知道具体代理的方法的 SQL 内容。同时,这个类也会解析和记录被代理方法的入参和出参,以方便对 SQL 的查询占位符进行替换,同时对查询到的 SQL 结果进行转换。

二、执行SQL

代理类生成之后,就可以执行代理类的具体逻辑,也就是真正开始执行用户自定义的SQL逻辑了。

首先会进入到 MapperMethod 核心的执行逻辑,如下所示:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // ...
    return result;
}

通过代码我们可以很清晰地发现,为什么 MyBatis 的 insert、update 和 delete 会返回行数的原因。业务处理上,我们经常通过 update = 1 来判断当前语句是否更新成功。

这里一共做了两件事情,一件事情是通过 BoundSql 将方法的入参转换为 SQL 需要的入参形式,第二件事情就是通过 SqlSession 来执行对应的 Sql。下面我们通过 select 来举例。

三、缓存

SqlSession 是 MyBatis 对 SQL 执行的封装,真正的 SQL 处理逻辑要通过 Executor 来执行。Executor 有多个实现类,因为在查询之前,要先 check 缓存是否存在,所以默认使用的是 CachingExecutor 类,顾名思义,它的作用就是二级缓存。

CachingExecutor 的执行逻辑如下所示:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
                        ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 放缓存
                tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
        }
    }
    // 若二级缓存为空,则重新查询数据库
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

二级缓存是和命名空间绑定的,如果多表操作的 SQL 的话,是会出现脏数据的。同时如果是不同的事务,也可能引起脏读,所以要慎重。

如果二级缓存没有命中,则会进入到 BaseExecutor 中继续执行,在这个过程中,会调用一级缓存执行。

值得一提的是,在 MyBatis 中,缓存分为 PerpetualCache、BlockingCache、LruCache 等,这些 cache 的实现则是借用了装饰者模式。一级缓存使用的是 PerpetualCache,里面是一个简单的 HashMap。一级缓存会在更新的时候,事务提交或者回滚的时候被清空。换句话说,一级缓存是和 SqlSession 绑定的。

四、查询数据库

如果一级缓存中没有的话,则需要调用JDBC执行真正的SQL逻辑。我们知道,在调用JDBC之前,是需要建立连接的,如下代码所示:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

我们会发现,Mybatis并不是直接从JDBC获取连接的,通过数据源来获取的,Mybatis默认提供了是那种数据源:JNDI,PooledDataSource和UnpooledDataSource,我们也可以引入第三方数据源,如Druid等。包括驱动等都是通过数据源获取的。

获取到Connection之后,还不够,因为JDBC的数据库操作是需要Statement的,所以Mybatis专门抽象出来了 StatementHandler 处理类来专门处理和JDBC的交互,如下所示:

SimpleStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
}

其实这三代代码就代表了Mybatis执行SQL的核心逻辑:组装SQL,执行SQL,组装结果。仅此而已。

具体Sql是如何组装的呢?是通过BoundSql来完成的,具体组装的逻辑大家可以从 org.apache.ibatis.mapping.MappedStatement#getBoundSql 中了解,这里不再赘述。

五、处理查询结果

当我们获取到查询结果之后,就需要对查询结果进行封装,即把查询到的数据库字段映射为DO对象。

因为此时我们已经拿到了执行结果ResultSet,同时我们也在应用启动的时候在配置文件中配置了DO到数据库字段的映射ResultMap,所以通过这两个配置就可以转换。核心的转换逻辑是通过TypeHandler完成的,流程如下所示:

  1. 创建返回的实体类对象,如果该类是延迟加载,则先生成代理类
  2. 根据ResultMap中配置的数据库字段,将该字段从ResultSet取出来
  3. 从ResultMap中获取映射关系,如果没有,则默认将下划线转为驼峰式命名来映射
  4. 通过setter方法反射调用,将数据库的值设置到实体类对象当中

到此这篇关于一文搞懂Mybatis的工作原理的文章就介绍到这了,更多相关mybatis工作原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java利用Spire.Doc for Java实现在Word文档中插入图片

    Java利用Spire.Doc for Java实现在Word文档中插入图片

    本文将深入探讨如何利用 Spire.Doc for Java 这一强大库,在 Java 程序中高效、灵活地实现 Word 文档的图片插入功能,包括不同环绕方式和指定位置的插入,有需要的小伙伴可以了解下
    2025-10-10
  • Java JUC中操作List安全类的集合案例

    Java JUC中操作List安全类的集合案例

    这篇文章主要介绍了JUC中操作List安全类的集合案例,本文罗列了不安全的集合和安全的集合进行对比,以及Java中提供的安全措施,需要的朋友可以参考下
    2021-07-07
  • java 三元操作符用法说明

    java 三元操作符用法说明

    这篇文章主要介绍了java 三元操作符用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 如何基于Springboot完成新增员工功能并设置全局异常处理器

    如何基于Springboot完成新增员工功能并设置全局异常处理器

    最近工作中遇到了做一个管理员工信息的功能,下面这篇文章主要给大家介绍了关于如何基于Springboot完成新增员工功能并设置全局异常处理器的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • Mybatis批量提交实现步骤详解

    Mybatis批量提交实现步骤详解

    这篇文章主要介绍了Mybatis批量提交实现步骤详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • MyBatis查询 、修改 、删除操作示例代码

    MyBatis查询 、修改 、删除操作示例代码

    MyBatis 作为一款灵活的持久层框架,提供了直接编写 SQL 语句的能力,避免了其他 ORM 框架可能带来的性能和功能限制,本文介绍 MyBatis 中如何高效执行这三种操作,并通过代码示例展示最佳实践,感兴趣的朋友一起看看吧
    2024-08-08
  • MyBatis官方代码生成工具给力(解放双手)

    MyBatis官方代码生成工具给力(解放双手)

    这篇文章主要介绍了MyBatis官方代码生成工具给力(解放双手),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • SpringAOP切入点规范及获取方法参数的实现

    SpringAOP切入点规范及获取方法参数的实现

    这篇文章主要介绍了SpringAOP切入点规范及获取方法参数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java 深入浅出掌握Map集合之双列集合

    Java 深入浅出掌握Map集合之双列集合

    双列集合是每个元素都有键与值两部分组成的集合,记录的是键值对对应关系,即通过键可以找到值,键必须是唯一,值可以重复,接下来跟着小编具体了解吧
    2021-11-11
  • java必学必会之线程(1)

    java必学必会之线程(1)

    java必学必会之线程第一篇,介绍了线程的基本概念、线程的创建和启动,想要学好java线程的朋友一定要好好阅读这篇文章
    2015-12-12

最新评论