你是否好奇:为什么MyBatis只需一个XML文件和一个接口就能操作数据库?这背后隐藏着Java动态代理的黑科技!本文将用最直观的方式为你揭开这个魔法面纱。
一、先看神奇现象 ✨
// 只有接口,没有实现类!
public interface UserDao {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(int id);
}
// 直接使用接口操作数据库
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findById(1); // 实际执行了SQL查询!
没有实现类却能执行数据库操作!这就是MyBatis的魔法。下面我们拆解这个魔法背后的原理。
二、核心原理全景图
MyBatis核心流程:
- 接口声明:定义数据库操作方法
- XML映射:提供SQL实现
- 动态代理:运行时生成接口实现
- SQL执行:将方法调用转为数据库操作
三、核心组件解析 🔍
1. 四大金刚组件
组件 | 作用 | 生命周期 |
---|---|---|
SqlSessionFactory | 初始化配置,创建SqlSession | 应用启动创建,关闭销毁 |
SqlSession | 执行SQL的核心会话 | 请求开始创建,响应关闭 |
MapperRegistry | 管理所有Mapper接口的注册 | 随SqlSessionFactory |
MapperProxy | 动态代理的实际执行者(魔法核心) | 每次getMapper时创建 |
2. 配置文件加载过程
四、动态代理机制详解 🪄
1. 代理创建流程图
2. 代理执行过程
// 伪代码展示代理执行逻辑
public class MapperProxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) {
// 1. 获取方法对应的SQL语句
String sqlId = method.getDeclaringClass().getName() + "." + method.getName();
String sql = sqlMap.get(sqlId);
// 2. 获取SQL类型(SELECT/UPDATE等)
SqlCommandType type = getSqlCommandType(method);
// 3. 执行数据库操作
switch(type) {
case SELECT:
return executeQuery(sql, args);
case UPDATE:
return executeUpdate(sql, args);
// ...其他操作
}
}
}
五、XML映射文件解析 🗺️
1. XML与接口的绑定方式
绑定方式 | 示例 | 特点 |
---|---|---|
XML命名空间绑定 | <mapper namespace="com.dao.UserDao"> | 传统方式,明确绑定 |
注解绑定 | @Select("SELECT * FROM users") | 简洁,适合简单SQL |
混合绑定 | XML定义复杂SQL + 注解定义简单操作 | 灵活组合 |
2. XML解析关键步骤
<!-- UserMapper.xml -->
<mapper namespace="com.example.UserDao">
<!-- SQL映射 -->
<select id="findById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 方法名与SQL的映射关系 -->
<select id="findAll" resultType="User">
SELECT * FROM users
</select>
</mapper>
解析过程:
- 读取
namespace
确定接口全限定名 - 解析
<select>
等标签的id
属性对应方法名 - 建立接口方法 ⇄ SQL语句的映射关系
六、完整执行流程演示 🚀
1. 执行序列图
2. 代码层次解析
// 层次调用关系
userDao.findById(1) // 客户端调用
-> MapperProxy.invoke() // 代理拦截
-> MapperMethod.execute() // 方法执行
-> DefaultSqlSession.selectOne() // 会话操作
-> CachingExecutor.query() // 执行器(含缓存)
-> SimpleExecutor.doQuery() // 实际查询
-> StatementHandler.query() // 语句处理
-> JDBC操作数据库
七、核心问题解答 ❓
1. 为什么不需要实现类?
MyBatis在运行时通过动态代理生成接口的实现类,开发者无需手动编写实现代码。
2. 方法如何关联SQL?
通过全限定名+方法名作为唯一ID匹配XML中的SQL定义:
接口方法 => com.example.UserDao.findById
XML映射 => <mapper namespace="com.example.UserDao">
<select id="findById">...
3. 参数如何传递?
// 接口方法定义
User findByConditions(@Param("name") String name,
@Param("age") int age);
// XML中使用
<select id="findByConditions" resultType="User">
SELECT * FROM users
WHERE name=#{name} AND age>#{age}
</select>
MyBatis会自动将参数包装成Map传递给SQL执行器。
八、高级技巧:自定义拦截器 🔧
可以插入自定义逻辑扩展MyBatis:
// 实现分页拦截器
@Intercepts({
@Signature(type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
// 1. 检测分页参数
// 2. 修改原始SQL添加LIMIT
// 3. 执行查询
// 4. 返回分页结果
}
}
在配置文件中注册:
<plugins>
<plugin interceptor="com.example.PageInterceptor"/>
</plugins>
九、总结:MyBatis魔法揭秘
- 动态代理是核心:运行时生成接口实现
- 映射绑定是关键:通过namespace+id关联接口与SQL
- 配置解析是基础:启动时加载XML建立方法-SQL映射
- 分层执行是保障:代理→会话→执行器→JDBC的完整链路
💡 设计精髓:约定优于配置 + 动态代理 = 极简API
最后的小测试:当调用userDao.findById(1)
时,实际执行的是哪个类的方法?
A) UserDaoImpl
B) MapperProxy
C) DefaultSqlSession
D) SimpleExecutor
(答案:B - 动态代理对象MapperProxy拦截了方法调用)
理解了这个原理,你就掌握了MyBatis的灵魂! 🚀