MyBatis-Plus 通用 CRUD 实现原理技术文档

MyBatis-Plus 通用 CRUD 实现原理技术文档

版本:基于 MyBatis-Plus 3.5.5(JDK 17+)

面向:需要阅读源码、定制 BaseMapper、排查"魔法 SQL" 来源的开发人员


1 文档目标

  1. 说明 通用 CRUD(BaseMapper、IService) 在启动期如何 无 SQL 注入
  2. 拆解 SQL 模板 → BoundSql → JDBC 的完整链路
  3. 给出 自定义通用方法 的扩展点与示例

2 术语表

术语 说明
TableInfo MyBatis-Plus 对表元数据的运行时封装(表名、主键、字段、填充器等)
SqlInjector 将自定义方法模板注入到 Configuration 的策略接口
AbstractMethod 一个通用 SQL 方法的最小单元(如 SelectById、Insert)
MapperBuilderAssistant MyBatis 原生助手,负责把 MappedStatement 注册到 Configuration

3 启动期注入流程

3.1 入口:MybatisPlusAutoConfiguration

java 复制代码
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    return new MybatisPlusInterceptor();   // 插件链
}
  • 自动配置类会读取 mybatis-plus.global-config.dbConfig.*,填充到 GlobalConfig → DbConfig
  • 随后触发 MybatisConfiguration#addInterceptor() 把 DefaultSqlInjector 注册为 Bean。

3.2 DefaultSqlInjector 的职责

java 复制代码
public class DefaultSqlInjector extends AbstractSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            new DeleteById(),
            new UpdateById(),
            new SelectById(),
            ...
        ).collect(Collectors.toList());
    }
}
  • 每个 AbstractMethod 实现 两个核心方法
    • mappedStatement() 构建 MappedStatement
    • sqlMethod() 返回 String 模板(占位符由 TableInfo 填充)

3.3 模板 → MappedStatement

以 Insert 为例:

java 复制代码
public class Insert extends AbstractMethod {
    @Override
    public String sqlMethod(SqlMethod sqlMethod) {
        return "<script>INSERT INTO %s (%s) VALUES (%s)</script>";
    }

    @Override
    public SqlCommandType sqlCommandType() {
        return SqlCommandType.INSERT;
    }

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql = String.format(sqlMethod(SqlMethod.INSERT_ONE),
                tableInfo.getTableName(),
                sqlSelectColumns(tableInfo, false),
                sqlInsertColumns(tableInfo, false, null));
        return addInsertMappedStatement(mapperClass, modelClass, sqlMethod(), sql);
    }
}
  • 占位符 %s 由 TableInfo 动态替换 → 运行时无 XML
  • 最终通过 MapperBuilderAssistant 把 MappedStatement 注册到 Configuration,ID = mapper接口全名 + "." + methodName
    例如 com.demo.mapper.UserMapper.insert

4 运行期执行链路

4.1 调用栈

复制代码
userMapper.insert(user)
 ├─ MapperProxy.invoke()
 ├─ MapperMethod.execute()
 ├─ SqlSession.insert()
 ├─ BaseExecutor.update()
 ├─ SimpleExecutor.doUpdate()
 ├─ PreparedStatementHandler.update()
 └─ JDBC PreparedStatement.execute()

4.2 SQL 模板填充

  • 占位符解析 发生在 SqlSource#getBoundSql()
  • 字段过滤 (逻辑删除、填充器)由 TableInfo 在 ParameterHandler 阶段完成
  • 乐观锁 会在 beforeUpdate() 中追加 version = version + 1

5 扩展:自定义通用方法

5.1 步骤

  1. 继承 AbstractMethod
  2. 注册到自定义 SqlInjector
  3. 在 Mapper 或 Service 层调用

5.2 实战:批量软删除

java 复制代码
public class LogicDeleteBatchByIds extends AbstractMethod {
    @Override
    public String sqlMethod(SqlMethod sqlMethod) {
        return "<script>UPDATE %s SET deleted = 1 WHERE id IN (%s)</script>";
    }

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String ids = "#{" + COLLECTION + "}";
        String sql = String.format(sqlMethod(SqlMethod.LOGIC_DELETE_BATCH_BY_IDS),
                tableInfo.getTableName(), ids);
        return addUpdateMappedStatement(mapperClass, modelClass, sqlMethod(), sql);
    }
}

@Mapper
public interface UserMapper extends BaseMapper<User> {
    int logicDeleteBatchByIds(@Param("ids") List<Long> ids);
}

6 常见问题排查清单

现象 排查命令 可能原因
方法不存在 Configuration#getMappedStatementIds 未注册 SqlInjector
SQL 字段缺失 打印 TableInfo#getAllInsertSqlColumn 实体未加 @TableField
主键未回填 检查 TableInfo#getKeyProperty 实体主键未加 @TableId
逻辑删除失效 查看 TableInfo#isLogicDelete 全局配置未开启 logicDeleteField

7 结论

  • 通用 CRUD ≠ 魔法:启动期注入模板 + 运行期 TableInfo 填充
  • 可插拔 :通过 SqlInjector/AbstractMethod 可在任意 Mapper 上追加新方法
  • 零侵入:不改变 MyBatis 原生执行链,便于与原生 XML 混合使用

深入 TableInfo 与 SqlInjector 源码,即可在团队内快速构建"私有通用方法库",同时保持与 MyBatis-Plus 官方升级兼容。

相关推荐
小蒜学长4 分钟前
基于SpringBoot的佳新超市管理系统设计与实现系统(代码+数据库+LW)
java·数据库·spring boot·后端·佳新超市管理系统
sunshine22 girl28 分钟前
Java学习六 Java常见API-1-Radom随机数
java·开发语言·学习
wangbing11251 小时前
使用jps jstack排查系统问题
java
于指尖飞舞1 小时前
java面试题总结
java·开发语言
ZealSinger2 小时前
LangChain4j用AiServices拼Tool
java·rag·ai智能体·langchain4j
vx_Biye_Design2 小时前
springboot高校选课系统82776-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·课程设计·express
Sweet锦3 小时前
不调 Python,不装向量库:我用纯 Java 写了一套以图搜图引擎
java·人工智能·开源·图搜索
如意猴4 小时前
【C++】007 C/C++ 内存管理机制、malloc与new的区别及模板初阶
java·c++·算法
可乐鸡翅yeah_4 小时前
HLS 分片过期清理,直播旧 TS 分片磁盘爆满问题处理
java·后端·spring·m3u8·m3u8在线·音视频在线播放
专业程序开发源4 小时前
SSM校园拍摄交流服务平台36936-计算机课程设计、毕业设计
java·spring boot·后端·python·elasticsearch·php·课程设计