1. MyBatis映射器模块架构解析
MyBatis映射器模块的核心由三个关键组件构成:MapperRegistry、MapperProxyFactory和MapperProxy。MapperRegistry作为注册中心,维护了所有已加载的Mapper接口与对应MapperProxyFactory的映射关系。当首次访问某个Mapper接口时,MyBatis会通过动态代理机制创建MapperProxy实例,这个代理对象才是真正处理SQL请求的入口。
关键设计原则:采用JDK动态代理而非CGLIB,既减少了依赖又保证了接口代理的纯粹性。每个Mapper方法调用都会被转换为Method对象,进而触发后续的SQL处理流程。
映射器的工作流程可分为四个阶段:
- 初始化阶段:解析XML配置或注解,构建MappedStatement
- 代理创建阶段:为每个Mapper接口生成动态代理
- 方法调用阶段:拦截接口方法调用
- SQL执行阶段:通过执行器完成数据库操作
2. 源码级执行流程拆解
2.1 方法调用拦截机制
当调用Mapper接口方法时,实际进入的是MapperProxy的invoke方法。核心处理逻辑如下:
java复制public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
这里有两个关键分支:
- 基础Object方法直接反射调用
- 自定义Mapper方法进入缓存查询和执行流程
2.2 SQL参数处理流程
参数处理的核心类是ParamNameResolver,其工作流程包含三个关键步骤:
- 获取方法参数名(通过编译时参数或@Param注解)
- 构建参数名与实参的映射关系
- 根据参数位置生成最终参数集合
参数处理常见问题解决方案:
| 问题类型 | 解决方案 | 配置示例 |
|---|---|---|
| 多参数未命名 | 添加@Param注解 | void update(@Param("id") Long id, @Param("name") String name) |
| 集合参数 | 使用foreach标签 | <foreach item="item" collection="list"> |
| 复杂对象 | 直接属性引用 | # |
3. 高级映射场景实践
3.1 嵌套结果映射优化
对于复杂的一对多关系查询,推荐使用嵌套结果映射而非多次查询:
xml复制<resultMap id="blogResultMap" type="Blog">
<id property="id" column="blog_id"/>
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
</collection>
</resultMap>
性能优化建议:
- 确保主表id列在SELECT语句中排在首位
- 使用columnPrefix处理字段名冲突
- 对于大数据集考虑使用分步查询(association/collection的select属性)
3.2 动态SQL最佳实践
动态SQL的几种高效写法对比:
xml复制<!-- 推荐写法:使用where标签自动处理前缀 -->
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM blog
<where>
<if test="state != null">state = #{state}</if>
<if test="title != null">AND title like #{title}</if>
</where>
</select>
<!-- 批量插入优化方案 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user(name, age) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>
4. 生产环境问题排查指南
4.1 常见异常处理
-
BindingException排查步骤:
- 检查XML namespace与Mapper接口全限定名是否一致
- 确认方法名是否拼写正确
- 验证是否存在对应的SQL语句定义
-
Executor异常处理经验:
- BatchExecutor需要手动flush
- ReuseExecutor的语句缓存可能导致内存泄漏
- 分页查询时注意ResultSet的关闭时机
4.2 性能监控方案
通过自定义Interceptor实现SQL监控:
java复制@Intercepts({
@Signature(type= Executor.class, method="update", args={MappedStatement.class,Object.class}),
@Signature(type= Executor.class, method="query", args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
})
public class PerformanceInterceptor implements Interceptor {
private static final long SLOW_QUERY_THRESHOLD = 1000;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
if (duration > SLOW_QUERY_THRESHOLD) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
log.warn("Slow query detected: {} took {}ms", ms.getId(), duration);
}
}
}
}
5. 扩展开发技巧
5.1 自定义类型处理器
处理JSON字段的TypeHandler实现示例:
java复制public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private final Class<T> type;
private final ObjectMapper objectMapper = new ObjectMapper();
public JsonTypeHandler(Class<T> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, objectMapper.writeValueAsString(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return json == null ? null : objectMapper.readValue(json, type);
}
}
注册方式:
xml复制<typeHandlers>
<typeHandler handler="com.example.JsonTypeHandler" javaType="com.example.UserInfo"/>
</typeHandlers>
5.2 插件开发实战
实现分页拦截器的关键代码:
java复制@Intercepts(@Signature(type= StatementHandler.class,
method="prepare", args={Connection.class,Integer.class}))
public class PaginationInterceptor implements Interceptor {
private static final ThreadLocal<PageInfo> PAGE_INFO = new ThreadLocal<>();
public static void startPage(int pageNum, int pageSize) {
PAGE_INFO.set(new PageInfo(pageNum, pageSize));
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
PageInfo pageInfo = PAGE_INFO.get();
if (pageInfo == null) {
return invocation.proceed();
}
StatementHandler handler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = handler.getBoundSql();
String originalSql = boundSql.getSql();
String countSql = "SELECT COUNT(*) FROM (" + originalSql + ") temp";
// 执行count查询...
String pageSql = originalSql + " LIMIT " + pageInfo.getOffset() + "," + pageInfo.getPageSize();
// 修改boundSql...
try {
return invocation.proceed();
} finally {
PAGE_INFO.remove();
}
}
}
在实际项目中,Mapper接口的批量操作性能往往成为瓶颈。通过将批量更新拆分为多个小批次(建议每批500-1000条),可以显著降低内存消耗。对于MySQL数据库,推荐在JDBC连接字符串中添加rewriteBatchedStatements=true参数,这能使批量插入性能提升5-10倍
