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。这种设计让代码更加简洁,并能在编译期就通过接口发现拼写错误,提升了开发体验。
下面简述下代理模式:
代理模式代码:
java
// 目标接口
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 动态代理实现:
java
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+具体执行流程分析
浏览器访问如下方法:
java
// 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 方法
java
// 入参
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)

(5)this.sqlSessionProxy.selectOne(statement, parameter) 也是调用 JDK动态代理实现

看下 SqlSessionInterceptor 中的 invoke 方法
java
@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); 这个方法
java
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(..)
java
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> 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);
java
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
继续调用 this.selectList(statement, parameter, RowBounds.DEFAULT)
java
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
继续调用 selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
java
private <E> List<E> 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);
java
@Override
public <E> List<E> 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 以及深度性能调优等高级场景,从而在微服务架构中构建出更稳健、高效的数据访问层。

