引言
Java项目开发中,只要使用了关系型数据库,几乎都会用到Mybatis框架。从本文起,我们一起来看看Mybatis的源码实现,从而深入掌握这个框架。
本文从JDBC规范说起,直接使用java.sql包编程,展示了使用底层API做实战开发不可避免的痛点;当引入Mybatis时,又是如何的高效、灵活。进而介绍了Mybatis框架的功能架构和配置解析流程。
一 什么是JDBC
JDBC(Java Database Connectivity)是Java中访问关系型数据库的一套规范化API,将Java应用中使用它,可以执行SQL语句、检索结果和更改数据等。
1.1 示例
Java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* JDBC 查询 MySQL 用户
*/
public class JdbcExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
private static final String DB_USER = "root"; // 你的数据库用户名
private static final String DB_PASSWORD = "123456"; // 你的数据库密码
public static void main(String[] args) throws ClassNotFoundException {
// 1. 加载驱动(MySQL 8.0+ 可省略,Driver 类会自动注册)
Class.forName("com.mysql.cj.jdbc.Driver");
try (
// 获取数据库连接
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
// 预编译 SQL(防止 SQL 注入)
PreparedStatement pstmt = conn.prepareStatement("SELECT id, username, age, email FROM user WHERE age > ?");
) {
// 3. 设置 SQL 参数(索引从 1 开始)
pstmt.setInt(1, 20);
// 4. 执行查询,获取结果集
ResultSet rs = pstmt.executeQuery();
// 5. 遍历结果集
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
int age = rs.getInt("age");
String email = rs.getString("email");
System.out.printf("ID: %d, 用户名: %s, 年龄: %d, 邮箱: %s%n", id, username, age, email);
}
} catch (SQLException e) {
System.err.println("JDBC 操作异常:");
}
}
}
上面示例中出现了JDBC中几个核心类:Driver、DriverManager、Connection、PreparedStatement、ResultSet
| 核心类 | 核心作用 |
|---|---|
Driver |
注册驱动,建立通信桥梁,适配不同数据库(如MySQL/Oracle) |
DriverManager |
管理驱动,获取数据库连接 |
Connection |
代表数据库物理连接,所有操作入口,实现事务管理、创建Statement |
PreparedStatement |
Statement接口实现,预编译 SQL,能防注入、提升性能 |
ResultSet |
存储查询结果,提供取值能力 |
1.2 不足之处
在项目实战中,使用示例代码操作数据库,存在很多不足。
| 不足点 | 具体表现 |
|---|---|
| 代码冗余且重复 | ① 每次操作需重复写加载驱动、获取连接、创建 Statement、关闭资源等模板代码;② 遍历 ResultSet 封装用户对象 |
| SQL 与 Java 代码耦合 | SQL 语句硬编码在 Java 代码中,修改 SQL 需重新编译 Java 代码。 |
| 手动处理参数 / 结果集 | ① 参数需手动绑定,参数索引易出错;② 结果集需手动封装为对象,字段映射繁琐且易漏。 |
| 资源管理易出问题 | 用 try-with-resources,在 finally 手动关闭Connection/ResultSet |
| 事务管理繁琐 | 需手动调用conn.setAutoCommit(false)/commit()/rollback(),多表操作时事务控制代码分散。 |
| 无缓存 / 性能优化能力 | 每次查询均直接访问数据库,无 SQL 缓存、结果缓存机制;分页、批量操作需手写 SQL |
| 数据库适配性差 | 切换数据库时,需修改连接 URL、驱动类、SQL 语法(如分页、函数),代码改动量大。 |
直接使用 JDBC 相当于 "手动搬砖",需关注所有底层细节(资源、参数、SQL、事务),开发效率低、易出错、维护成本高。
因此,Mybatis出现了,它 封装了 JDBC 所有冗余且易出错的细节 ,既解决上述痛点,又避免了全自动化 ORM(如 Hibernate)对 SQL 的过度封装,是 Java 项目中数据库操作的最优解之一。
二 初识Mybatis
MyBatis 作为 JDBC 的轻量级封装框架,有以下核心价值:
- 消除模板代码,提升开发效率
- MyBatis 封装了连接获取、资源关闭、Statement 创建等冗余逻辑,通过
SqlSession统一管理操作入口,无需重复编写模板代码; - 内置结果集自动映射:MyBatis 可自动将 ResultSet 字段映射到实体类属性,无需手动
getXxx()封装。
- SQL 与代码解耦,便于维护
- SQL 集中写在 XML 映射文件或注解中,修改 SQL 无需编译 Java 代码;
- 支持动态 SQL(
<if>/<where>/<foreach>),解决 JDBC 中拼接 SQL 的痛点
- 参数 / 结果集自动处理,降低出错率
- 参数绑定:支持按名称(如
#{age})绑定,无需关注参数索引,避免setInt(1, 20)的索引错误; - 结果映射:支持一对一、一对多关联查询的自动映射(如用户关联订单),无需手动嵌套遍历 ResultSet。
- 内置防 SQL 注入机制
- MyBatis 默认使用
#{}参数占位符(底层封装 PreparedStatement),自动规避 SQL 注入;
- 自动化资源与事务管理
- 集成连接池(如 HikariCP),自动管理 Connection 的创建 / 释放,避免连接泄漏;
- 支持声明式事务(如 Spring+MyBatis 中
@Transactional注解),无需手动调用commit()/rollback()。
- 内置性能优化能力
- 支持一级缓存(SqlSession 级别)、二级缓存(Mapper 级别),减少重复查询;
- 提供分页插件(如 PageHelper),无需手写
LIMIT语句,分页逻辑统一管理。
- 良好的数据库适配性
- 通过配置不同的数据库方言(dialect),可快速切换数据库,SQL 语法差异由框架适配(如分页、主键生成策略)。
Mybatis的业务架构如下

来看一个使用示例
java
import com.learn.more.entiry.User;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class JdbcDemo {
public static void main(String[] args) throws IOException {
Reader reader = null;
SqlSession session = null;
try {
reader = Resources.getResourceAsReader("mybatis‐config.xml");
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
session = sqlMapper.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);
System.out.println(user.toString());
} catch (Exception e) {
} finally {
if (reader != null) {
reader.close();
}
if (session != null) {
session.close();
}
}
}
}
三 Mybatis启动流程
Mybatis启动过程,就是解析配置文件、初始化基础组件,最终创建SqlSession的过程。

3.1 解析主配置文件
使用Mybatis时,有两个配置文件;一个用于声明Mybatis运行参数,一个用于声明CRUD语句的mapper文件。
先来看Mybatis的主配置文件。如下,从中可以获取环境信息、Mapper文件列表、全局设置、类型别名、类型处理器、插件配置等。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="org/apache/ibatis/databases/blog/blog-derby.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
</typeAliases>
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
</typeHandlers>
<objectFactory />
<plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="100"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
</mappers>
</configuration>
源码中是如何处理上述配置的呢?如下代码:
java
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
来看SqlSessionFactoryBuilder#build方法,它会将XML中配置信息转化为Configuration对象。

使用XMLConfigBuilder#parseConfiguration方法,逐个处理XML标签,最终构建Configuration对象。处理XML文件时使用了org.w3c.dom、org.xml.sax等依赖包。

Configuration类持有所有配置信息,部分如下:

3.1.1 全局属性
对于一些全局属性,未配置时有默认值。在使用Mybatis时需要关注。

最后,使用Configuration对象创建一个SqlSessionFactory实例。

3.1.2 类型别名注册
TypeAliasRegistry类,用于保存简化名到Java类的映射;这使得我们可以在Mapper.xml中配置如resultType="int",而不用java.lang.Integer全类名。
创建TypeAliasRegistry时自行注册了常用Java类型的别名。

创建Configuration对象时,默认注册了mybatis框架自身需要的一些别名。这也是能配置如的原因。

3.1.3 类型处理器
TypeHandler用于定义javaType与jdbcType之间相会转换逻辑。
java
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP =
new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP =
new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
比如要将MySQL中的TINYINT处理成Java中的Byte,将使用ByteTypeHandler。

3.2 解析Mapper文件
在主配置文件中,mapper文件有多种配置方式。
java
<configuration>
<mappers>
<!-- 指定Mapper XML文件的相对路径 -->
<mapper resource="com/example/mapper/UserMapper.xml"/>
<!-- 接口类路径,要求接口与 XML 同路径同名 -->
<mapper class="com.example.mapper.UserMapper"/>
<mapper url="file:///D:/project/com/example/mapper/UserMapper.xml"/>
<!-- 扫描指定包下的所有Mapper接口(需满足接口与XML同路径同名) -->
<package name="com.example.mapper"/>
</mappers>
</configuration>
在XMLConfigBuilder#mapperElement中,分别处理了以上几种配置方式:

源码中会通过XMLMapperBuilder,来完成Mapper文件解析。同时将Mapper.xml文件对应的接口,注册到Configuration。
已下面这个文件为例,是对User信息的CRUD。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserById" parameterType="java.lang.Integer" resultType="com.example.pojo.User">
SELECT id, username, age, create_time FROM user WHERE id = #{id}
</select>
<insert id="addUser" parameterType="com.example.pojo.User">
INSERT INTO user (username, age, create_time)
VALUES (#{username}, #{age}, #{createTime})
</insert>
<update id="updateUser" parameterType="com.example.pojo.User">
UPDATE user
SET username = #{username},
age = #{age},
create_time = #{createTime}
WHERE id = #{id}
</update>
<delete id="deleteUserById" parameterType="java.lang.Integer">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
在XMLMapperBuilder#configurationElement中,解析了各个标签。

一个Mapper文件的解析结果,会按照用途被保存到Configuration中:
- Configuration#mapperRegistry属性,记录着mapper接口与其代理工厂

- 解析XML中的CRUD语句,封装为MappedStatement对象,保存在Configuration#mappedStatements属性中

- 将resultMap保存在Configuration#resultMaps

- 将parameterMap保存在Configuration#parameterMaps

3.3 创建SqlSession
经过以上步骤,创建了一个包含了MyBatis需要的所有配置Configration对象,接着会创建一个
SqlSessionFactory对象。
java
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
SqlSessionFactory接口有两个实现:SqlSessionManager、DefaultSqlSessionFactory。两者的差异在于:
DefaultSqlSessionFactory
- 纯工厂模式:专注于创建SqlSession实例
- 无状态设计:线程安全,可被多线程共享
- 简单直接:每次调用openSession()都创建新的SqlSession
SqlSessionManager
- 工厂+会话双重身份:既是SqlSessionFactory又是SqlSession
- 会话管理:提供托管会话功能,支持ThreadLocal会话绑定
- 代理模式:内部使用动态代理处理SqlSession操作
SqlSessionManager内部委托给DefaultSqlSessionFactory,本质上是对DefaultSqlSessionFactory的功能扩展,通过代理模式提供了更高级的会话管理能力:一个线程中始终使用同一个SqlSession。
Mybatis中默认使用DefaultSqlSessionFactory。通过以下步骤,将创建一个SqlSession对象。
