深度剖析MyBatis:核心类的奥秘与强大功能
在 Java 持久层框架的世界里,MyBatis 以其灵活、高效的特性备受开发者青睐。今天,让我们一同深入探究 MyBatis 中几个至关重要的类,揭开它们的神秘面纱,领略 MyBatis 的强大魅力。
一、MappedStatement:SQL 语句的映射使者
(一)功能概述
MappedStatement 在 MyBatis 框架中扮演着关键角色,它犹如一座桥梁,将 XML 文件中的 SQL 语句节点(如 <select>、<update>、<insert> 标签)与 Java 代码紧密相连。在 MyBatis 框架初始化阶段,会对 XML 配置文件进行深度扫描和解析,将其中的 SQL 语句节点逐一转化为一个个 MappedStatement 对象,从而构建起 SQL 语句与代码逻辑之间的映射关系。

(二)实例解析
以一个简单的 XML Mapper 文件为例:
<?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="mybatis.UserDao">
<cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>
<resultMap id="userResultMap" type="UserBean">
<id property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="createDate" column="create_date"/>
</resultMap>
<select id="find" parameterType="UserBean" resultMap="userResultMap">
select * from user
<where>
<if test="userName!=null and userName!=''">
and user_name = #{userName}
</if>
<if test="userPassword!=null and userPassword!=''">
and user_password = #{userPassword}
</if>
<if test="createDate!=null">
and create_date = #{createDate}
</if>
</where>
</select>
<select id="find2" parameterType="UserBean" resultMap="userResultMap">
select * from user
<where>
<if test="userName!=null and userName!=''">
and user_name = #{userName}
</if>
<if test="userPassword!=null and userPassword!=''">
and user_password = #{userPassword}
</if>
<if test="createDate!=null">
and create_date = #{createDate}
</if>
</where>
</select>
</mapper>MyBatis 解析该文件后,会注册两个 MappedStatement 对象,分别对应 id 为 find 和 find2 的 <select> 节点。在 MyBatis 框架中,为了确保每个 MappedStatement 对象的唯一性,其标识采用 Mapper 文件的 namespace 加上节点本身的 id 值。例如,上述两个 MappedStatement 对象在 MyBatis 框架中的唯一标识分别是 mybatis.UserDao.find 和 mybatis.UserDao.find2。
(三)源码探秘
查看 MappedStatement 对象的源码,我们可以看到其中包含了众多属性,这些属性与 XML 元素的属性存在着紧密的对应关系。其中,比较关键的属性包括:
- ParameterMap 对象:用于表示查询参数,它明确了输入参数的类型和映射关系,确保 SQL 语句在执行时能够正确获取参数值。
- ResultMap 列表(resultMaps):负责定义 SQL 查询结果与 Java 对象之间的映射规则,使得 MyBatis 能够将从数据库中获取的数据准确无误地封装成 JavaBean 对象,方便在 Java 代码中进行处理。
- SqlSource 对象:这是最为重要的属性之一,它承担着执行动态 SQL 计算和获取的重任。通过这个对象,
MappedStatement能够根据用户提供的查询参数对象,动态生成要执行的 SQL 语句,充分展现了 MyBatis 的灵活性。
(四)工作流程
MappedStatement 对象的工作流程清晰而高效。当用户发起查询请求时,它首先接收用户传递的查询参数对象,然后借助 SqlSource 对象,根据参数对象的具体值动态计算出实际要执行的 SQL 语句。接着,将计算好的 SQL 语句发送到数据库执行,获取查询结果。最后,利用 ResultMap 列表将查询结果封装为 JavaBean 对象,并返回给用户。这个过程完美体现了 MyBatis 的核心价值:“根据用户提供的查询参数对象,动态执行 SQL 语句,并将结果封装为 Java 对象”。
(五)类图展示
class MappedStatement {
- resource: String
- configuration: Configuration
- id: String
- fetchSize: Integer
- timeout: Integer
- statementType: StatementType
- resultSetType: ResultSetType
- sqlSource: SqlSource
- cache: Cache
- parameterMap: ParameterMap
- resultMaps: List<ResultMap>
- flushCacheRequired: boolean
- useCache: boolean
- resultOrdered: boolean
- sqlCommandType: SqlCommandType
- keyGenerator: KeyGenerator
- keyProperties: String[]
- keyColumns: String[]
- hasNestedResultMaps: boolean
- databaseId: String
- statementLog: Log
- lang: LanguageDriver
+ MappedStatement()
}二、SqlSource:动态 SQL 的幕后大师
(一)接口定义
SqlSource 是一个接口类,在 MappedStatement 对象中作为一个关键属性存在。其代码如下:
package org.apache.ibatis.mapping;
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}这个接口仅有一个方法 getBoundSql(Object parameterObject),该方法返回一个 BoundSql 对象。BoundSql 对象代表了一次 SQL 语句的实际执行内容,而 SqlSource 对象的核心职责就是根据传入的参数对象,动态计算并生成这个 BoundSql 对象。
(二)常用实现类
SqlSource 最常用的实现类是 DynamicSqlSource,其代码如下:
package org.apache.ibatis.scripting.xmltags;
import java.util.Map;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.session.Configuration;
public class DynamicSqlSource implements SqlSource {
private Configuration configuration;
private SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}在 getBoundSql 方法中,通过创建 DynamicContext 对象,并调用 rootSqlNode.apply(context) 启动了一个基于递归实现的动态计算 SQL 语句的过程。这个过程借助 OGNL 根据传入的参数对象计算表达式,从而生成该次调用过程中实际要执行的 SQL 语句。
(三)动态 SQL 计算过程
- 首先,创建
DynamicContext对象,传入Configuration和参数对象。DynamicContext会对参数对象进行"Map 化”处理,即将传入的 POJO 对象转换为一个类似 Map 的数据结构,以便后续统一使用 Map 接口方法来访问数据。 - 接着,调用
rootSqlNode.apply(context),这是动态计算 SQL 语句的核心步骤。rootSqlNode会根据传入的参数对象,通过 OGNL 计算表达式,逐步构建出实际要执行的 SQL 语句,并将其添加到DynamicContext的sqlBuilder中。 - 然后,创建
SqlSourceBuilder对象,使用它来解析DynamicContext中的 SQL 语句和参数类型,生成一个新的SqlSource对象。 - 最后,从新生成的
SqlSource对象中获取BoundSql对象,并将DynamicContext中的绑定参数设置到BoundSql对象中,最终返回BoundSql对象,代表了这次动态计算得到的实际 SQL 语句和相关参数。
(四)类图展示
interface SqlSource {
+ getBoundSql(Object parameterObject): BoundSql
}
class DynamicSqlSource {
- configuration: Configuration
- rootSqlNode: SqlNode
+ DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode)
+ getBoundSql(Object parameterObject): BoundSql
}三、DynamicContext:参数处理的核心枢纽
(一)功能介绍
DynamicContext 类在 MyBatis 的参数处理和动态 SQL 计算过程中起着至关重要的作用。它主要负责对传入的参数对象进行处理,将其转换为适合动态 SQL 计算的格式,并提供了一系列方法用于操作和获取 SQL 相关的信息。
(二)源码分析
- 在
DynamicContext的构造函数中,根据传入的参数对象是否为Map类型,有两种不同的处理方式来构造ContextMap对象。ContextMap是一个继承自HashMap的内部类,其作用是统一参数的访问方式,使得无论是普通的 POJO 对象还是Map对象,都可以通过Map接口方法来访问数据。 - 当传入的参数对象不是
Map类型时,MyBatis 会使用MetaObject对象对其进行封装。在动态计算 SQL 过程中,当需要获取数据时,通过Map接口的get方法包装MetaObject对象的取值过程,从而实现对 POJO 对象属性的访问。 DynamicContext类中的静态初始块static { OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor()); }表明 MyBatis 使用 OGNL 来计算动态 SQL 语句。ContextAccessor是DynamicContext的内部类,实现了 OGNL 中的PropertyAccessor接口,为 OGNL 提供了如何使用ContextMap参数对象的具体说明,从而完成了整个参数对象的"Map 化”处理。
(三)参数传递和使用过程
- 传入的参数对象首先被统一封装为
ContextMap对象。 - OGNL 运行时环境在动态计算 SQL 语句时,按照
ContextAccessor中描述的Map接口方式来访问和读取ContextMap对象,获取计算过程中所需的参数。 ContextMap对象内部可能封装了一个普通的 POJO 对象,也可能是直接传递的Map对象,但从外部来看,都是通过Map接口来读取数据,实现了对不同类型参数的无差别化处理。
(四)类图展示
class DynamicContext {
- bindings: ContextMap
- sqlBuilder: StringBuilder
- uniqueNumber: int
+ PARAMETER_OBJECT_KEY: String
+ DATABASE_ID_KEY: String
+ DynamicContext(Configuration configuration, Object parameterObject)
+ getBindings(): Map<String, Object>
+ bind(String name, Object value)
+ appendSql(String sql)
+ getSql(): String
+ getUniqueNumber(): int
static class ContextMap {
- parameterMetaObject: MetaObject
+ ContextMap(MetaObject parameterMetaObject)
+ get(Object key): Object
}
static class ContextAccessor {
+ getProperty(Map context, Object target, Object name): Object
+ setProperty(Map context, Object target, Object name, Object value)
}
}(五)示例验证
以下是一个 JUnit 测试方法,用于验证 MyBatis 参数获取过程中对 Map 对象和普通 POJO 对象的无差别化处理:
@Test
public void testSqlSource() throws Exception {
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
Configuration configuration = session.getConfiguration();
MappedStatement mappedStatement = configuration
.getMappedStatement("mybatis.UserDao.find2");
assertNotNull(mappedStatement);
UserBean param = new UserBean();
param.setUserName("admin");
param.setUserPassword("admin");
BoundSql boundSql = mappedStatement.getBoundSql(param);
String sql = boundSql.getSql();
Map<String, Object> map = new HashMap<>();
map.put("userName", "admin");
map.put("userPassword", "admin");
BoundSql boundSql2 = mappedStatement.getBoundSql(map);
String sql2 = boundSql2.getSql();
assertEquals(sql, sql2);
UserBean bean = session.selectOne("mybatis.UserDao.find2", map);
assertNotNull(bean);
} finally {
session.close();
}
}在这个测试中,第一次使用 UserBean 对象获取和计算 SQL 语句,第二次使用 HashMap 对象进行同样的操作,甚至直接使用 HashMap 对象启动了一次 session 对象的查询。测试结果通过,充分说明了 MyBatis 在参数获取过程中,对 Map 对象和普通 POJO 对象的无差别化处理,因为在内部,两者都会被封装,然后通过 Map 接口来访问。
总结
通过对 MyBatis 中这几个重要类的深入剖析,我们清晰地了解了 MyBatis 的核心工作机制。MappedStatement 作为 SQL 语句的映射使者,协调了 SQL 与 Java 代码之间的关系;SqlSource 则是动态 SQL 的幕后大师,根据参数动态生成可执行的 SQL 语句;DynamicContext 作为参数处理的核心枢纽,确保了参数的统一访问和动态 SQL 计算的顺利进行。这些类相互协作,共同构建了 MyBatis 强大而灵活的持久层框架,为开发者提供了高效、便捷的数据库操作体验。
说明:本文基于 MyBatis 3.x 版本进行剖析,核心类结构与机制在后续版本中保持相对稳定,但具体实现细节可能随版本迭代有所调整。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/shen-du-pou-xi-mybatis-he-xin-lei-de-ao-mi-yu-qiang-da-gong-neng.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。