揭秘MyBatis核心类MappedStatement
MappedStatement-封装一次sql映射语句的所有配置信息
- MappedStatement: 是 MyBatis 框架中核心类
核心功能:封装一次 SQL 映射语句(SQL Statement)的所有配置信息
使用了内部静态类 Builder 来实现构建者模式, 保证 MappedStatement 的不可变性和构造过程的灵活性
作用:
MappedStatement : 代表 MyBatis 中一个具体的映射语句配置,包含了执行 SQL 所需的所有信息
MappedStatement.Builder
是用于构建 MappedStatement
实例的构造器,避免构造参数过多,代码易读且易扩展。
public static class Builder {private MappedStatement mappedStatement = new MappedStatement();public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {// 必填参数初始化及默认值赋值}// 各种 setter 风格的方法链,方便链式调用public Builder resource(String resource) { ... }public Builder fetchSize(Integer fetchSize) { ... }// ... 省略public MappedStatement build() {// 校验必须字段非空// 包装不可变的 List 等return mappedStatement;}
}
主要字段说明
字段名 | 类型 | 作用描述 |
---|---|---|
resource | String | 映射语句来源资源(一般为 XML 路径) |
configuration | Configuration | MyBatis 全局配置对象 |
id | String | 映射语句唯一标识(namespace + id) |
fetchSize | Integer | 批量获取结果集大小 |
timeout | Integer | 执行超时时间 |
statementType | StatementType | SQL 语句类型(STATEMENT, PREPARED, CALLABLE) |
resultSetType | ResultSetType | 结果集类型(FORWARD_ONLY 等) |
sqlSource | SqlSource | SQL 语句提供者,负责动态 SQL 解析 |
cache | Cache | 缓存策略 |
parameterMap | ParameterMap | 参数映射 |
resultMaps | List<ResultMap> | 结果映射列表 |
flushCacheRequired | boolean | 是否需要刷新缓存 |
useCache | boolean | 是否使用二级缓存 |
resultOrdered | boolean | 结果是否有序 |
sqlCommandType | SqlCommandType | SQL 类型(SELECT, INSERT, UPDATE, DELETE) |
keyGenerator | KeyGenerator | 主键生成器 |
keyProperties | String[] | 主键属性列表 |
keyColumns | String[] | 主键列列表 |
hasNestedResultMaps | boolean | 是否存在嵌套的结果映射 |
databaseId | String | 数据库标识,支持多数据库映射 |
statementLog | Log | 语句执行日志 |
lang | LanguageDriver | 语言驱动,处理不同脚本语言(XML、注解等) |
resultSets | String[] | 多结果集名称 |
Builder
中常用方法详解
parameterMap(ParameterMap parameterMap)
:指定参数映射。resultMaps(List<ResultMap> resultMaps)
:指定结果映射,并根据结果映射判断是否有嵌套结果映射。keyGenerator(KeyGenerator keyGenerator)
:指定主键生成策略,默认根据配置和 SQL 类型确定。keyProperty(String keyProperty)
/keyColumn(String keyColumn)
:指定主键对应的属性名和列名。lang(LanguageDriver driver)
:指定语言驱动,如 XML 语言驱动。build()
:构造最终的不可变MappedStatement
实例。
关键方法
- 根据参数对象,从
sqlSource
解析出最终 SQL 和参数映射。 - 如果没有参数映射,则使用默认参数映射重构。
- 进一步检查参数映射中是否有嵌套结果映射,标记
hasNestedResultMaps
。
public BoundSql getBoundSql(Object parameterObject) {BoundSql boundSql = sqlSource.getBoundSql(parameterObject);List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings == null || parameterMappings.isEmpty()) {boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);}// 检查参数映射中是否包含嵌套结果映射(解决 issue #30)for (ParameterMapping pm : boundSql.getParameterMappings()) {String rmId = pm.getResultMapId();if (rmId != null) {ResultMap rm = configuration.getResultMap(rmId);if (rm != null) {hasNestedResultMaps |= rm.hasNestedResultMaps();}}}return boundSql;
}
此方法不能用的原因
在用 MyBatis-Plus + MybatisInterceptor 链机制,而你注册的是 原始 MyBatis 插件机制
MyBatis-Plus 3.4.0+ 默认只支持 InnerInterceptor
注册的是 MyBatis 原生的插件(实现了 org.apache.ibatis.plugin.Interceptor
),
而 MyBatis-Plus 的默认插件执行链不包含原生 Interceptor
,必须你通过手动注册。
你可以从日志中看到这些被拦截的类是:
复制编辑
plugin target: class com.baomidou.mybatisplus.core.MybatisParameterHandler
这是 MyBatis-Plus 定制过的执行器链条(它使用了自己的 InterceptorChain
)。
故整体变动改为实现 InnerInterceptor
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) {// 判断是否跳过if (MerchantIsolationContext.isSkipped()) {return;}try {String originalSql = boundSql.getSql().toLowerCase(Locale.ROOT);log.info("===> into MerchantIsolationInnerInterceptor, msId = {}, SQL = {}", ms.getId(), originalSql);if (!originalSql.contains("from") || originalSql.contains(MerchantCenterConstant.FIELD_MERCHANT_ID)) {return;}Class<?> doClass = extractDoClass(ms);if (CheckUtil.isEmpty(doClass)) {log.warn("Unable to extract DO class, skipping merchant_id injection");return;}if (!hasMerchantId(doClass)) {return;}if (isMultiTable(originalSql)) {log.warn("Multi-table SQL detected, skipping merchant_id injection: {}", originalSql);return;}Long merchantId = extractMerchantId(parameter);if (CheckUtil.isEmpty(merchantId)) {throw BaseIllegalArgumentException.newException(BaseIllegalArgumentException.BASE_ILLEGAL_ARGUMENT,"No merchant_id provided and no skip claim");}String modifiedSql = appendMerchantCondition(boundSql.getSql(), merchantId);log.info("SQL injection merchant_id completed: {}", modifiedSql);// 替换 SQL(MyBatis-Plus 特殊处理)ReflectUtil.setFieldValue(boundSql, "sql", modifiedSql);} catch (Exception e) {log.error("MerchantIsolationInnerInterceptor Error: {}", e.getMessage(), e);}
}