从底层源码深入剖析 MyBatis 工作原理
MyBatis 并非传统的全自动 ORM 框架它更像一个 SQL 映射工具在保留 SQL 灵活性的同时通过 XML 或注解将 SQL 与 Java 对象进行绑定 。这种“半自动”的设计哲学让它在一线互联网大厂中应用广泛成为 Java 开发者必须掌握的核心技能之一。一、三层架构各司其职的精密协作MyBatis 采用经典的分层架构设计从外到内分为接口层、核心处理层和基础支撑层每一层都有其明确的职责如同工厂的流水线高效协同。1、接口层面向开发者的“窗口”这是开发者打交道最多的一层核心对象是SqlSession和Mapper 接口。接口层一接收到调用请求就会调用下一层来完成具体的数据处理。当你调用UserMapper.getById(1)时MyBatis 会通过动态代理机制生成一个 Mapper 实例底层最终通过SqlSession.select(“statementId”, parameter)来实现数据库操作。2、核心处理层真正的“生产线”这是 MyBatis 的心脏负责将接口层的请求转化为实际的数据库操作。它主要完成两件事构建动态 SQL通过传入的参数值使用 Ognl 动态构造 SQL 语句这是 MyBatis 灵活性和扩展性的关键。执行与结果映射执行 SQL 语句并将返回的结果集转换成 List 集合。这一层的关键角色包括负责调度的Executor、处理 SQL 语句的StatementHandler、设置参数的ParameterHandler以及处理结果集的ResultSetHandler。3、基础支撑层稳固的“地基”为上层提供最基础的通用功能支持包括连接管理、事务管理、配置加载和缓存处理等。例如其内置的一级缓存和二级缓存可以有效拦截部分数据库请求减少数据库压力提升系统性能。二、如何掌握MyBatis的工作原理结合JDBC来理解MyBatis的工作原理往往会更透彻。我们知道JDBC有四个核心对象DriverManager用于注册数据库连接Connection与数据库连接对象Statement/PrepareStatement操作数据库SQL语句的对象ResultSet结果集而MyBatis也有四大核心对象SqlSession对象该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。Executor接口它将根据SqlSession传递的参数动态地生成需要执行的SQL语句同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。MappedStatement对象该对象是对映射SQL的封装用于存储要映射的SQL语句的id、参数等信息。ResultHandler对象用于对返回的结果进行处理最终得到自己想要的数据格式或类型。可以自定义返回类型。在JDBC中Connection不直接执行SQL方法而是利用Statement或者PrepareStatement来执行方法。在使用JDBC建立了连接之后可以使用Connection接口的createStatement()方法来获取Statement对象也可以调用prepareStatement()方法获得PrepareStatement对象通过executeUpdate()方法执行。而在MyBatis中SqlSession对象包含了执行SQL语句的所有方法但是它是委托Executor执行的。从某种意义上来看MyBatis里面的SqlSession类似于JDBC中的Connection他们都是委托给其他类去执行。三、核心执行流程八步完成一次数据库操作MyBatis 的执行流程可以清晰地分为八个步骤从加载配置到返回结果每一步都环环相扣。1. 读取核心配置加载mybatis-config.xml全局配置文件最终被封装成Configuration对象。2. 加载映射文件加载 SQL 映射文件Mapper XML配置操作数据库的 SQL 语句。3. 构建会话工厂通过SqlSessionFactoryBuilder构建SqlSessionFactory其最佳作用域是应用作用域。4. 创建会话对象由工厂创建SqlSession它包含了执行 SQL 的所有方法。注意SqlSession 实例不是线程安全的。5. 获取执行器MyBatis 底层通过Executor接口来具体操作数据库它是调度的核心。6. 获取 SQL 声明MappedStatement封装了一条 SQL 语句的所有信息包括 SQL 本身、入参和出参映射规则。7. 输入参数映射ParameterHandler将用户传入的 Java 对象参数转换成 JDBC 的 PreparedStatement 所需要的参数。8. 封装结果集ResultSetHandler将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的 Java 对象集合。四、MyBatis底层原理MyBatis 最巧妙的设计之一就是无需编写 Mapper 接口的实现类。这背后的魔法正是JDK 动态代理。当调用session.getMapper(UserMapper.class)时MyBatis 会通过MapperProxyFactory为这个接口生成一个代理对象。你调用的接口方法会被代理对象拦截然后根据“接口全限定名 方法名”作为 id去找到对应的MappedStatement最终由Executor执行具体的 SQL。这种设计让代码更加简洁并能在编译期就通过接口发现拼写错误提升了开发体验。下面简述下代理模式代理模式代码// 目标接口 interface ISubject { void request(); } // 目标类 class RealSubject implements ISubject { public void request() { System.out.println(RealSubject: Handling request.); } } // 代理对象类 class Proxy implements ISubject { private RealSubject realSubject; public Proxy() { this.realSubject new RealSubject(); } public void request() { System.out.println(Proxy: Logging the time of request.); realSubject.request(); } } // 使用代理 public class Client { public static void main(String[] args) { ISubject proxy new Proxy(); proxy.request(); } }JDK 动态代理实现import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定义接口 interface MyInterface { void hello(); } // 定义接口实现类 class MyObject implements MyInterface { Override public void hello() { System.out.println(hello); } } // 实现 InvocationHandler 接口 class MyInvocationHandler implements InvocationHandler { private final Object obj; public MyInvocationHandler(Object obj) { this.obj obj; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(Before invoking method.getName()); Object result method.invoke(obj, args); System.out.println(After invoking method.getName()); return result; } } public class Main { public static void main(String[] args) { // 目标类 MyObject obj new MyObject(); // 把目标类传入 MyInvocationHandler 构造方法 MyInvocationHandler handler new MyInvocationHandler(obj); // 把 目标类 与 InvocationHandler 接口交由 Proxy 代理 MyInterface proxy (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class?[]{MyInterface.class},handler); // 代理访问 proxy.hello(); } }在这个例子中MyObject 是一个实现 MyInterface 接口的类它的 myMethod 方法会被代理。MyInvocationHandler 是一个实现 InvocationHandler 接口的类它会在代理对象的方法被调用前后执行一些额外的逻辑。Main 类是一个简单的测试程序它会创建一个代理对象并调用代理对象的方法。运行这个程序将输出以下内容Before invoking myMethod hello After invoking myMethod可以看到在调用 proxy.hello() 方法之前和之后MyInvocationHandler 中定义的逻辑会被执行。这种动态代理的实现方式可以很方便地在运行时动态地生成代理对象并在代理对象的方法被调用前后执行一些额外的逻辑。五、MyBatis 底层用到了两重 JDK 动态代理以下是基于MyBatis3.5具体执行流程分析浏览器访问如下方法// http://localhost:6060/findUserById/1 GetMapping(/findUserById/{id}) public User findUserById(PathVariable(id) Long id) { return userService.findUserById(id); }1目标接口 UserDAO 交由代理类 MapperProxy 代理看下 MapperProxy 代理类都有哪些成员变量SqlSessionTemplate 是 SqlSession 的实现类2调用 MapperProxy 中 invoke 方法里面调用 cachedInvoker 方法// 入参 new PlainMethodInvoker(new MapperMethod(UserDAO, findUserById, MyBatis 配置信息))解析出 mybatis 配置文件信息如下3调用 return cachedInvoker(method).invoke(proxy, method, args, sqlSession) 后面中的 invoke 方法4调用 MapperMethod.execute 方法会执行 case SELECT 逻辑继续执行 sqlSession.selectOne(..)逻辑由实现类 SqlSessionTemplate 来实现 selectOne(String statement, Object parameter)5this.sqlSessionProxy.selectOne(statement, parameter) 也是调用 JDK动态代理实现看下 SqlSessionInterceptor 中的 invoke 方法Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取 SqlSession SqlSession sqlSession getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator ! null unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession null; Throwable translated SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated ! null) { unwrapped translated; } } throw unwrapped; } finally { // 关闭资源 if (sqlSession ! null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }继续看下 SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); 这个方法public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); // 通过 SqlSessionFactory 获取 SqlSession // 已经存在 SqlSessionHolder holder (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session sessionHolder(executorType, holder); if (session ! null) { return session; } // 第一次访问需要 openSession LOGGER.debug(() - Creating a new SqlSession); session sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }6再分析下 核心方法 Object result method.invoke(sqlSession, args)真实会调用 DefaultSqlSession.selectOne(..)Override public T T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. ListT list this.selectList(statement, parameter); if (list.size() 1) { return list.get(0); } else if (list.size() 1) { throw new TooManyResultsException(Expected one result (or null) to be returned by selectOne(), but found: list.size()); } else { return null; } }继续调用 List list this.selectList(statement, parameter);Override public E ListE selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }继续调用 this.selectList(statement, parameter, RowBounds.DEFAULT)Override public E ListE selectList(String statement, Object parameter, RowBounds rowBounds) { return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER); }继续调用 selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);private E ListE selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException(Error querying database. Cause: e, e); } finally { ErrorContext.instance().reset(); } }终于看到 Executor.query 方法了继续执行 return executor.query(ms, wrapCollection(parameter), rowBounds, handler);Override public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql ms.getBoundSql(parameterObject); CacheKey key createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }继续执行 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);继续执行 list delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);继续执行 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException继续执行 list doQuery(ms, parameter, rowBounds, resultHandler, boundSql);程序执行到这终于看到我们熟悉的 PrepareStatement 对象了/ 对应 JDBC 编程的第三步骤继续执行 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException继续继续执行 handler.prepare(connection, transaction.getTimeout())程序继续执行 return delegate.prepare(connection, transactionTimeout);初始化 Statement执行查询至此Mybatis 运行原理整套流程已经分析完毕。六、性能调优核心策略理解了原理才能更好地进行优化。MyBatis 的性能调优是一个系统工程主要集中在以下几个关键点优化方向核心策略实战价值SQL 语句优化避免 SELECT *合理使用索引优化多表关联。查询 10 万条数据优化后速度可提升近 10 倍。缓存策略运用合理使用一级缓存SqlSession 级别和二级缓存namespace 级别。高并发下缓存命中率高的查询平均响应时间可从 200ms 降至 50ms。批量操作优化使用foreach标签或 BatchExecutor 进行批量插入、更新。批量插入 1000 条数据耗时可从 5 秒缩短至 0.5 秒。参数合理配置调整fetchSize每次抓取行数、连接池参数如maxPoolSize。合理设置fetchSize大数据量查询性能可提升数倍。需要注意的是调优时要避免误区例如过度依赖缓存可能导致数据不一致盲目增大fetchSize可能引发内存溢出。七、从原理到实践MyBatis 的设计启示MyBatis 的成功在于其“半自动化”的精准定位。它没有试图完全屏蔽 SQL而是将 SQL 的控制权交还给开发者同时通过动态代理、分层架构和丰富的插件机制极大地简化了 JDBC 的繁琐操作。这种设计哲学启示我们优秀的框架不是大包大揽而是在提供便利的同时保留足够的灵活性和控制力。对于正在使用 Spring Boot 和 MyBatis-Plus 的你来说理解 MyBatis 的核心原理能让你更从容地应对复杂查询优化、定制化 SQL 以及深度性能调优等高级场景从而在微服务架构中构建出更稳健、高效的数据访问层。