🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我。
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
目录
-
深入MyBatis内核:徒手打造SqlSession的CRUD圣殿
-
告别代理,直击本质:手写MyBatis SqlSession的增删改查奥秘
-
MyBatis源码进阶(一):从SqlSession的CRUD接口看框架执行脉络
-
"硬核"操作:在手写MyBatis中实现最底层的SQL执行API
-
架构师视角:为什么SqlSession的直接CRUD API是MyBatis灵活性的基石?
正文
在我们日常使用MyBatis进行开发时,最熟悉的莫过于定义Mapper接口并配合@Select
、@Insert
等注解或XML文件来轻松实现数据库操作。这背后是MyBatis强大的动态代理机制在默默工作。但你是否曾想过,抛开这层"魔法",MyBatis最核心、最底层的执行单元究竟是什么?今天,我们就将深入MyBatis的内核,亲手实现SqlSession
接口中的直接CRUD方法,探究其原理、价值与设计哲学。
一、为何需要非Mapper代理的API?
在回答如何实现之前,我们必须先理解为什么要提供这样一种看似"繁琐"的API。Mapper代理的方式固然优雅,但它并非万能。
-
极致灵活性与动态性 :在某些高度动态的场景中,SQL语句的
statementId
(即namespace.id
)甚至SQL片段本身都可能是在运行时根据业务逻辑生成的。此时,我们无法在编码阶段定义出固定的Mapper接口方法。通过SqlSession
直接传入statementId
的方式,可以灵活地执行任何在配置中定义的SQL映射。 -
理解框架执行脉络 :Mapper代理只是一个"外观模式"(Facade Pattern)的优雅实现,它最终调用的仍然是
SqlSession
的selectOne
、insert
等方法。学习直接使用这些方法,有助于我们更深层次地理解MyBatis的执行流程和数据流转,这对于排查复杂问题、进行性能调优至关重要。 -
遗留系统或特殊集成:在一些古老的系统或者特殊的集成需求中,可能无法按照MyBatis的约定来定义接口。这种底层API提供了更大的集成空间。
-
框架扩展的基石 :许多基于MyBatis的增强框架或中间件(如分库分表组件),其内部往往需要绕过Mapper代理,直接与最核心的
Executor
和SqlSession
打交道。理解这一层,是进行二次开发的基础。
正因为其底层和灵活的特性,SqlSession
实例的生命周期管理就显得尤为重要。它通常不是线程安全的,因此其生命周期应被限定在一次请求或一个方法作用域 内(即"请求级别")。通过SqlSessionFactory
来创建,在使用后务必通过close()
方法将其关闭,以防止数据库连接泄漏。这也是为什么我们常用try-with-resources
语句来确保其被正确关闭。
二、核心架构与执行流程剖析
在开始手写代码之前,我们先通过一张图来俯瞰整个执行流程,这有助于理解各个组件是如何协同工作的:

如图所示,SqlSession
是API的入口,但它只是一个"指挥中心",真正的执行工作委托给了Executor
。而Executor
又会进一步将参数处理、SQL执行、结果集映射等任务分发给各自的专职组件(StatementHandler
, ParameterHandler
, ResultSetHandler
)。整个流程清晰且职责分明,是经典职责链模式的体现。
我们的核心任务,就是在DefaultSqlSession
中实现这个"指挥"的逻辑。
三、手写实现:从接口定义到具体执行
现在,让我们开始动手,一步步实现图中的流程。
第一步:定义SqlSession接口的CRUD方法
我们在SqlSession
接口中直接定义增删改查方法。
java
public interface SqlSession extends Closeable {
/**
* 检索一条记录
* @param statementId namespace.id
* @param parameter 参数对象
* @return 单条结果对象
*/
<T> T selectOne(String statementId, Object parameter);
/**
* 插入一条记录
* @param statementId namespace.id
* @param parameter 参数对象
* @return 影响的行数
*/
int insert(String statementId, Object parameter);
// 类似定义 update, delete 方法
int update(String statementId, Object parameter);
int delete(String statementId, Object parameter);
// ... 其他方法如 getMapper, getConfiguration, commit, rollback 等
}
第二步:在DefaultSqlSession中实现这些方法
DefaultSqlSession
是接口的默认实现,它持有Configuration
和Executor
的引用,所有方法的核心逻辑都是:
-
通过
Configuration
获取对应的MappedStatement
。 -
将参数和
MappedStatement
交给Executor
去执行。
java
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T selectOne(String statementId, Object parameter) {
// 本质上就是调用selectList,然后取第一条结果
List<T> list = this.selectList(statementId, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statementId, Object parameter) {
// 1. 核心:根据statementId从配置中获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statementId);
// 2. 委托给Executor执行
return executor.query(ms, parameter);
}
@Override
public int insert(String statementId, Object parameter) {
// 增删改本质上都是update操作
return update(statementId, parameter);
}
@Override
public int update(String statementId, Object parameter) {
MappedStatement ms = configuration.getMappedStatement(statementId);
return executor.update(ms, parameter);
}
@Override
public int delete(String statementId, Object parameter) {
return update(statementId, parameter);
}
// ... 关闭、提交、回滚等方法
}
关键点剖析:
-
MappedStatement
:它是MyBatis的核心数据结构之一,是一个不可变 的对象。它完整地描述了一条SQL语句的所有信息:SQL源、执行类型(SELECT/UPDATE等)、输入参数映射、输出结果映射、缓存策略、ID等。通过statementId
(格式为namespace.id
)从Configuration
这个全局配置容器中获取它,是执行的第一步。 -
Executor
:它是真正的执行器 ,是MyBatis的调度核心 。SqlSession
的所有数据库操作命令都委托给它来完成。它定义了query
、update
、commit
、rollback
等方法。我们的默认实现(SimpleExecutor
)会处理整个执行流程:-
创建
StatementHandler
(它负责创建PreparedStatement
)。 -
创建
ParameterHandler
(它负责将用户传入的参数按规则设置到SQL中)。 -
执行语句。
-
创建
ResultSetHandler
(它负责将返回的ResultSet
转换成Java对象集合)。
-
-
一致性 :注意到
insert
、update
、delete
最终都调用了executor.update()
。这说明在JDBC层面和MyBatis底层视角中,只有SELECT
和UPDATE
两种操作 (INSERT
和DELETE
在JDBC中也属于更新操作)。这种设计简化了内部实现。
四、实战演示:如何使用底层API
假设我们在XML中配置了一个SQL:
XML
<mapper namespace="com.example.EmployeeMapper">
<select id="selectById" resultType="com.example.Employee">
SELECT * FROM employee WHERE id = #{id}
</select>
</mapper>
使用Mapper代理方式,我们会这样调用:
java
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.selectById(1);
而使用我们今天实现的底层API,则可以这样调用:
java
// 注意:statementId 必须是 "namespace.id" 的全限定名
String statementId = "com.example.EmployeeMapper.selectById";
Employee emp = sqlSession.selectOne(statementId, 1);
可以看到,这种方式确实更为繁琐,需要手动拼接字符串且容易出错,但它赋予了我们在运行时动态选择SQL的巨大灵活性。
五、总结与思考
通过手写SqlSession
的CRUD方法,我们剥离了MyBatis的动态代理"外衣",直击其核心执行引擎。我们看到了SqlSession
如何作为API门面,Configuration
如何作为配置中枢,Executor
如何作为调度核心,以及MappedStatement
如何作为SQL指令的载体。
这种底层API的存在,不仅是MyBatis框架灵活性和扩展性的体现,更是我们开发者深入理解框架、解决复杂问题的钥匙。它告诉我们,任何一个优秀的框架,其高层抽象的便捷性必然构建在底层坚实的灵活性和可扩展性之上。
下次当你轻松地调用一个Mapper接口方法时,不妨在脑海中回顾一下这条清晰的执行链:Interface -> Proxy -> SqlSession -> Executor -> JDBC Statement。这份理解,将让你在使用MyBatis时更加得心应手,充满自信。
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕我是程序员扣棣,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!