MyBatis框架源码解析
1. MyBatis工作原理解析
1.1 全局配置解析与SqlSessionFactory构建
MyBatis的启动流程始于对全局配置文件(mybatis-config.xml)的解析。该配置文件定义了数据库连接信息、事务管理器、类型别名、映射器(Mapper)等核心参数。MyBatis通过建造者模式(Builder Pattern)完成这一过程,核心类包括 SqlSessionFactoryBuilder 和 XMLConfigBuilder。
源码实现:SqlSessionFactoryBuilder.build()
// 示例:加载配置文件并创建SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
在底层源码中,SqlSessionFactoryBuilder.build() 方法通过 XMLConfigBuilder 解析 XML 配置文件,并将其封装为 Configuration 对象:
public SqlSessionFactory build(InputStream inputStream) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
return new DefaultSqlSessionFactory(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
}
}
- XMLConfigBuilder:负责解析
mybatis-config.xml中的各个标签(如<properties>、<settings>、<typeAliases>、<mappers>),并将解析结果填充到Configuration对象中。 - Configuration:全局配置的核心容器,存储了数据库连接信息、类型别名映射、Mapper注册信息等。
配置文件示例
<!-- mybatis-config.xml -->
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.example.model"/>
</typeAliases>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>
1.2 SqlSession的创建
SqlSessionFactory 是 MyBatis 的核心工厂接口,其 openSession() 方法通过工厂模式(Factory Pattern)创建 SqlSession 实例。SqlSession 代表一次数据库会话,用于执行 SQL 操作、管理事务等。
源码实现:SqlSessionFactory.openSession()
// 示例:通过SqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
底层实现中,DefaultSqlSessionFactory 默认实现了 SqlSessionFactory,openSession() 方法返回的是 DefaultSqlSession 实例:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
TransactionFactory transactionFactory = configuration.getTransactionFactory();
Transaction transaction = transactionFactory.newTransaction(dataSource, level, autoCommit);
Executor executor = configuration.newExecutor(transaction, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
1.3 Mapper接口代理的动态生成
MyBatis 的 Mapper 接口代理生成是其核心特性之一,通过 JDK 动态代理 将接口方法与 SQL 映射文件绑定。其底层逻辑依赖于 MapperProxyFactory 和 MapperProxy 类,结合 Configuration 中的 MappedStatement 实现方法调用的动态绑定。
1.3.1 获取 Mapper 实例
当我们调用 sqlSession.getMapper(UserMapper.class) 方法时,MyBatis 会尝试获取一个 UserMapper 的实例。这一步骤由 DefaultSqlSession 类中的 getMapper() 方法触发:
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
此方法内部委托给 Configuration 对象去获取具体的 Mapper 实例。
1.3.2 MapperRegistry 查找或创建 MapperProxyFactory
Configuration 类中的 getMapper() 方法进一步调用了 MapperRegistry 来查找或创建对应的 MapperProxyFactory:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
在 MapperRegistry.getMapper() 方法中,MyBatis 会检查是否已存在该类型(type)的 MapperProxyFactory,如果不存在,则抛出异常提示未注册的类型错误。如果找到了相应的工厂,则使用它来创建 Mapper 接口的代理对象。
1.3.3 动态生成 MapperProxy
一旦确定了 MapperProxyFactory,就会调用其 newInstance(sqlSession) 方法来创建 Mapper 接口的代理对象。这个过程中,MapperProxyFactory 使用 JDK 动态代理机制生成一个实现了指定 Mapper 接口的代理对象:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy
);
}
这里的 mapperProxy 是一个 MapperProxy 实例,它实现了 InvocationHandler 接口,负责拦截所有对 Mapper 接口方法的调用。
1.3.4 调用 Mapper 方法时的行为
当用户调用 Mapper 接口中的某个方法时(例如 userMapper.selectById(1)),JDK 动态代理机制会将该调用转发到 MapperProxy.invoke() 方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
}
以下是关键流程的详细分析:
(1) cachedInvoker() 方法的缓存机制
cachedInvoker() 方法用于从缓存中获取或创建 MapperMethodInvoker,避免重复解析相同的方法:
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// 处理 Java 8+ 的 default 方法
return new DefaultMethodInvoker(...);
} else {
// 普通方法:创建 PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
}
methodCache是一个ConcurrentHashMap<Method, MapperMethodInvoker>,用于缓存已解析的方法调用器。- 如果是普通方法(非 default),则创建
PlainMethodInvoker,其内部封装了一个MapperMethod实例。
(2) MapperMethod 的 SQL 映射解析
MapperMethod 的构造函数会通过 sqlSession.getConfiguration() 获取 MyBatis 的配置信息,并解析当前方法对应的 MappedStatement(即 XML 或注解中定义的 SQL 映射):
public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
this.command = new SqlCommand(configuration, mapperInterface, method);
this.method = method;
}
SqlCommand 解析 MappedStatement 的关键逻辑
SqlCommand 是 MapperMethod 的内部类,负责解析方法与 SQL 的映射关系。以下是其核心逻辑:
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 1. 尝试通过接口全名 + 方法名查找 MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
// 2. 如果没有找到,则检查是否是 @Flush 注解方法
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 3. 成功获取 MappedStatement
name = ms.getId(); // 即接口全名 + 方法名
type = ms.getSqlCommandType(); // SELECT/INSERT/UPDATE/DELETE 等
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
// 递归查找接口及父接口中的 MappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 递归检查父接口
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
resolveMappedStatement()的作用:- 构造
statementId:接口全名 + "." + 方法名(如com.example.UserMapper.selectById)。 - 从
Configuration中查找对应的MappedStatement。 - 如果当前接口未找到,递归检查其父接口(支持接口继承)。
- 如果最终未找到
MappedStatement,抛出BindingException异常(提示未绑定 SQL)。
- 构造
(3) MapperMethod.execute() 执行 SQL
一旦 MapperMethod 成功解析出 MappedStatement,调用 execute() 方法时会根据 SQL 类型(SELECT/INSERT 等)调用 SqlSession 的对应方法:
public Object execute(SqlSession sqlSession, Object[] args) {
switch (command.getType()) {
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
return null;
} else if (method.returnsMany()) {
return executeForMany(sqlSession, args);
} else {
return sqlSession.selectOne(command.getName(), param); // command.getName() 即 MappedStatement.id
}
// 其他 SQL 类型处理...
}
}
- 关键点:
command.getName()返回的是MappedStatement.id,即接口全名 + 方法名。SqlSession根据这个id找到具体的 SQL 映射,并通过Executor执行。
1.3.5 关键类及其职责总结
| 类名 | 主要职责 |
|---|---|
SqlSession |
提供与数据库交互的主要 API,包括获取 Mapper 实例等。 |
Configuration |
管理 MyBatis 配置信息,包括 Mapper 注册表等。 |
MapperRegistry |
注册并管理所有 Mapper 接口及其对应的 MapperProxyFactory。 |
MapperProxyFactory |
工厂类,负责创建 Mapper 接口的代理对象。 |
MapperProxy |
JDK 动态代理的 InvocationHandler 实现,拦截接口方法调用。 |
MapperMethodInvoker |
定义如何调用 Mapper 方法,有两种实现:PlainMethodInvoker 和 DefaultMethodInvoker。 |
MapperMethod |
封装 Mapper 接口方法与 SQL 映射之间的关系,处理参数解析、SQL 执行及结果映射。 |
1.4 SQL执行与结果映射
1.4.1 MappedStatement 的构建
MappedStatement 是 SQL 语句和映射关系的容器,其构建由 XMLMapperBuilder 完成:
public class XMLMapperBuilder extends BaseBuilder {
private void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
...
}
private void configurationElement(XNode context) {
String namespace = context.getStringAttribute("namespace");
try {
this.namespace = namespace;
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
namespace:XML 文件的namespace属性必须与 Mapper 接口全类名一致。MappedStatement的注册:public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); }
1.4.2 SQL 执行的底层流程
SQL 执行的核心在 Executor,其 query() 方法调用链如下:
// DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter) {
return executor.query(mappedStatement, wrapCollection(parameter));
}
// CachingExecutor
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);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
...
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
...
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
...
}
// SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 最终调用JDBC的接口
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
1.4.3 结果映射的实现
结果映射的核心逻辑在 ResultSetHandler,其 handleResultSets() 方法将 ResultSet 映射为 Java 对象:
public class DefaultResultSetHandler {
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<Object> multipleResults = new ArrayList<>();
processResultSet(rsw, resultMaps, multipleResults, null);
return collapseSingleResultList(multipleResults);
}
private void processResultSet(ResultSetWrapper rsw, List<ResultMap> resultMaps, List<Object> multipleResults, String columnPrefix) {
ResultMap resultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), rsw.getColumnNames());
DefaultResultHandler resultHandler = new DefaultResultHandler();
handleResultSet(rsw, resultMap, resultHandler, columnPrefix);
multipleResults.add(resultHandler.getResultObject());
}
}
ResultMap的自动创建:- 如果未显式定义
resultMap,MyBatis 会根据resultType自动生成隐式ResultMap。
- 如果未显式定义
- 字段与属性的匹配:
- 默认使用字段名与 JavaBean 属性名匹配(可配置
mapUnderscoreToCamelCase开启下划线转驼峰)。 - 自定义映射通过
<resultMap>指定字段与属性的对应关系。
- 默认使用字段名与 JavaBean 属性名匹配(可配置
1.5总结
MyBatis 的 Mapper 接口代理通过 JDK 动态代理实现,其核心在于 MapperProxy 对方法调用的拦截和 MappedStatement 的绑定。SQL 执行依赖 Executor 和 StatementHandler 的协作,结果映射则通过 ResultSetHandler 将数据库结果集转换为 Java 对象。这一流程贯穿了 MyBatis 的核心设计模式(如代理、策略模式)和关键数据结构(如 Configuration、MappedStatement)。

浙公网安备 33010602011771号