MyBatis-Plus 深度开发规范手册

张开发
2026/4/8 14:09:15 15 分钟阅读

分享文章

MyBatis-Plus 深度开发规范手册
引言在Java企业级开发中MyBatis-Plus作为MyBatis的增强工具凭借其“简化CRUD、提高开发效率”的核心定位已成为国内主流技术栈中不可或缺的一环。然而在实际项目落地过程中很多团队面临同样的问题代码风格不统一、规范执行不到位、性能隐患层出不穷。本文从工程实践出发结合阿里巴巴开发手册和一线大厂的最佳实践系统梳理MyBatis-Plus的开发规范旨在帮助团队建立统一、高效、可维护的数据访问层代码体系。一、核心架构分层规范1.1 分层调用规则强制遵循Controller → Service → Mapper的单向调用链。严禁 Service 之间循环依赖——若 A 需要调用 BB 也需要调用 A必须将公共逻辑抽取至第三个 Service 或 Manager 层。规范示例// 正确符合单向调用链 RestController public class OrderController { Autowired private OrderService orderService; // Controller调用Service } Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; // Service调用Mapper } // 错误Service之间直接调用 Service public class OrderServiceImpl { Autowired private UserService userService; // 避免这种跨Service直接依赖 }1.2 POJO 对象转换规范所有的 POJO 转换Entity ↔ DTO ↔ VO建议使用 MapStruct避免手动使用BeanUtils.copyProperties导致的反射性能损耗及字段名不对应的 Bug。// 推荐使用MapStruct Mapper(componentModel spring) public interface OrderConverter { OrderDTO toDTO(Order order); Order toEntity(OrderDTO orderDTO); } // 避免使用BeanUtils // BeanUtils.copyProperties(source, target); // 反射性能差字段名错位难以发现二、实体类与数据库建模规范2.1 主键策略规范核心主键使用Long类型配合雪花算法保证分布式场景下的全局唯一性并使用 Jackson 注解解决前端 JS 精度丢失问题。Data TableName(t_order) public class Order { TableId(type IdType.ASSIGN_ID) JsonSerialize(using ToStringSerializer.class) // 防止前端丢失后两位精度 private Long id; private String orderNo; private BigDecimal amount; }MyBatis-Plus 内置了雪花算法Snowflake只需在配置中指定type: ASSIGN_ID即可无需额外编码。2.2 乐观锁规范在高并发更新场景下必须使用乐观锁机制防止数据并发覆盖。数据库表增加 version 字段ALTER TABLE t_order ADD COLUMN version INT DEFAULT 0 COMMENT 乐观锁版本号;Entity 增加 Version 注解Data TableName(t_order) public class Order { TableId(type IdType.ASSIGN_ID) private Long id; Version private Integer version; // 标记为乐观锁字段 }插件配置Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }注意事项乐观锁插件只支持updateById和update方法业务代码中严禁手动修改 VersionMP 插件会自动完成版本号递增当update返回 0失败时需要决定是报错还是自动重试2.3 自动填充规范根据阿里巴巴开发手册数据库表必须包含create_time和update_time字段。通过 MP 的MetaObjectHandler实现自动填充。Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, createBy, Long.class, getCurrentUserId()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); this.strictUpdateFill(metaObject, updateBy, Long.class, getCurrentUserId()); } }2.4 逻辑删除规范业务数据不应物理删除应使用逻辑删除通过TableLogic注解实现。Data TableName(t_user) public class User { TableId(type IdType.ASSIGN_ID) private Long id; TableLogic private Integer deleted; // 0-未删除1-已删除 }注意事项手写 SQL 时MP 不会自动追加逻辑删除过滤条件需要在 XML 中手动加上AND deleted 0。2.5 枚举类型规范状态字段建议使用IEnum接口枚举避免代码中出现if (status 1)的硬编码。public enum StatusEnum implements IEnumInteger { DISABLED(0, 禁用), ACTIVE(1, 启用); private final int code; private final String desc; StatusEnum(int code, String desc) { this.code code; this.desc desc; } Override public Integer getValue() { return code; } }// 实体中使用枚举 Data TableName(t_user) public class User { private StatusEnum status; // 直接使用枚举类型MP自动映射 }三、CRUD 增强使用规范3.1 BaseMapper 使用规范Mapper 接口只需继承BaseMapperT即可自动获得 17 通用 CRUD 方法。Mapper public interface UserMapper extends BaseMapperUser { // 无需编写任何基础CRUD代码BaseMapper已全部提供 // 复杂查询可在此定义自定义方法 }BaseMapper 常用方法速查方法说明insert(T entity)插入一条记录deleteById(Serializable id)根据ID删除updateById(T entity)根据ID更新selectById(Serializable id)根据ID查询selectList(WrapperT wrapper)条件查询列表selectPage(IPageT page, WrapperT wrapper)分页查询3.2 IService / ServiceImpl 使用规范Service 层继承IService接口实现类继承ServiceImplM extends BaseMapperT, T获得更丰富的能力。// 接口 public interface UserService extends IServiceUser { // 业务方法定义 } // 实现类 Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { // 业务方法实现 // 基础CRUD无需编写直接使用 this.save(), this.list() 等方法 }3.3 批量操作规范❌ 错误做法在循环中调用 saveOrUpdate// 禁止每次循环都会先执行SELECT再决定INSERT/UPDATE for (User user : userList) { saveOrUpdate(user); // 严重性能问题 }✅ 正确做法使用批量操作方法MyBatis-Plus 提供了saveBatch、updateBatchById等批量操作方法基于 JDBC BatchUpdate 提升插入性能。// 批量插入默认批次大小1000 boolean result userService.saveBatch(userList); // 自定义批次大小 boolean result userService.saveBatch(userList, 500);性能对比传统 foreach 方式批量插入 1000 条数据耗时约 4200ms而 MP 的saveBatch仅需约 850ms性能提升近5 倍。3.4 条件构造器规范使用 Lambda 条件构造器替代字符串硬编码避免字段名重构引发运行时异常。// 推荐LambdaQueryWrapper类型安全 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(name), User::getName, name) .ge(age ! null, User::getAge, age) .orderByDesc(User::getCreateTime); ListUser users userMapper.selectList(wrapper); // 避免QueryWrapper 字符串硬编码字段名重构时编译不报错运行时报错 QueryWrapperUser wrapper new QueryWrapper(); wrapper.eq(name, name); // 字段名重构时风险注意事项使用saveOrUpdateBatch时每次判断存在都会先执行一次 SELECT容易导致数据库压力翻倍建议仅在数据量可控的场景下使用。四、高级功能规范4.1 分页查询规范MyBatis-Plus 的分页插件PaginationInnerInterceptor实现了物理分页自动识别 MySQL/Oracle/PostgreSQL 等方言并重写 SQL比传统 RowBounds 内存分页减少70%内存占用。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件必配 PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(); paginationInterceptor.setMaxLimit(500L); // 设置单页最大限制防止全表查询 paginationInterceptor.setOverflow(true); // 溢出总页数后自动回到第一页 interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }分页查询示例// 分页参数建议page1, size20~50 条/页 PageUser page new Page(current, size); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getStatus, StatusEnum.ACTIVE); PageUser result userMapper.selectPage(page, wrapper); // 获取数据 ListUser records result.getRecords(); long total result.getTotal();4.2 多数据源规范随着项目规模扩大单一数据源已无法满足复杂业务需求。MP 官方提供了dynamic-datasource开源方案支持读写分离、一主多从等场景。Maven 依赖dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version4.3.0/version /dependencyYAML 配置spring: datasource: dynamic: primary: master # 默认数据源 strict: false # 严格匹配数据源false则使用默认 datasource: master: url: jdbc:mysql://localhost:3306/db_master username: root password: 123456 slave_1: url: jdbc:mysql://localhost:3306/db_slave1 username: root password: 123456 slave_2: url: jdbc:mysql://localhost:3306/db_slave2 username: root password: 123456使用 DS 注解切换数据源Service DS(master) // 类级别指定默认使用主库 public class OrderServiceImpl implements OrderService { Override DS(slave) // 方法级别优先级更高从库查询 public ListOrder listOrders() { return this.list(); } }4.3 多租户支持规范对于 SaaS 应用可以通过TenantLineInnerInterceptor实现数据隔离避免在每个 SQL 中手动拼接租户 ID 条件。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 多租户插件 TenantLineInnerInterceptor tenantInterceptor new TenantLineInnerInterceptor(); tenantInterceptor.setTenantLineHandler(new TenantLineHandler() { Override public Expression getTenantId() { return new LongValue(TenantContextHolder.getCurrentTenantId()); } Override public String getTenantIdColumn() { return tenant_id; } Override public boolean ignoreTable(String tableName) { // 忽略不需要租户隔离的表如系统配置表 return sys_config.equals(tableName); } }); interceptor.addInnerInterceptor(tenantInterceptor); return interceptor; } }五、性能优化规范5.1 插件配置规范所有插件统一通过MybatisPlusInterceptor配置插件的执行顺序非常重要。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 1. 多租户插件最先执行用于注入租户条件 interceptor.addInnerInterceptor(new TenantLineInnerInterceptor()); // 2. 分页插件 PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(); paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); // 3. 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 4. 防止全表更新/删除插件生产环境必配 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); return interceptor; } }5.2 性能分析规范在开发测试环境使用性能分析工具监控 SQL 执行性能及时发现慢查询并进行优化。注意PerformanceInterceptor在 MP 3.2.0 及以上版本中已被移除推荐使用 P6Spy 替代。P6Spy 配置推荐dependency groupIdp6spy/groupId artifactIdp6spy/artifactId version3.9.1/version /dependency5.3 SQL 注入防护规范MyBatis-Plus 的条件构造器内置了 SQL 注入防护自动转义特殊字符但手写 XML 时必须警惕。强制规范不准在 Mapper.xml 中使用${}99% 的 SQL 注入源于此除 ORDER BY 动态排序外一律使用#{}不准在没有 WHERE 条件下执行 UPDATE/DELETE必须配置BlockAttackInnerInterceptor插件拦截不准在 IN 查询中传入超过 1000 个元素部分数据库有 1000 个上限且 SQL 过长导致索引失效六、事务管理规范6.1 声明式事务使用规范强制严禁在 Service 内部自调用标注了Transactional的方法会导致 AOP 事务失效。Service public class OrderServiceImpl implements OrderService { Autowired Lazy // 使用延迟加载避免启动时的循环依赖检测问题 private OrderService self; Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO orderDTO) { // 业务逻辑 } public void processOrder(OrderDTO orderDTO) { // 正确通过代理对象调用事务方法 self.createOrder(orderDTO); // 错误直接调用事务失效 // this.createOrder(orderDTO); } }6.2 事务边界规范强制事务方法内严禁进行网络 IO 操作如调用第三方支付接口、发邮件网络延迟会导致数据库连接长期不释放瞬间撑爆连接池。// 正确事务只负责数据库操作 Transactional public void updateOrderStatus(Long orderId, Integer status) { orderMapper.updateStatus(orderId, status); } // 错误事务中混入网络IO Transactional public void createOrder(Order order) { orderMapper.insert(order); httpClient.callThirdPartyAPI(); // 网络调用应放在事务外 sendEmail(); // 邮件发送应放在事务外 }规范声明式事务建议只加在UPDATE/INSERT方法上查询方法不加事务。七、代码编写“六不准”结合阿里巴巴开发规范和一线大厂最佳实践总结以下六条硬性军规序号军规原因1❌ 不准在循环中调用saveOrUpdate每次判断存在都会先执行 SELECT数据库压力翻倍2❌ 不准在 Mapper.xml 中使用${}99% 的 SQL 注入源于此动态排序除外3❌ 不准在没有 WHERE 条件下执行 UPDATE/DELETE必须配置BlockAttackInnerInterceptor拦截4❌ 不准在 IN 查询中传入超过 1000 个元素部分数据库有 1000 个上限且 SQL 过长导致索引失效5❌ 不准省略逻辑删除过滤条件手写 SQL 时需手动追加AND deleted 06❌ 不准使用BeanUtils.copyProperties进行 POJO 转换反射性能损耗大建议使用 MapStruct八、代码生成器规范8.1 代码生成器配置MyBatis-Plus 代码生成器可减少约70%的基础代码编写量但需要合理配置以符合团队规范。public class CodeGenerator { public static void main(String[] args) { // 1. 全局配置 GlobalConfig globalConfig new GlobalConfig(); globalConfig.setAuthor(YourName) .setOutputDir(System.getProperty(user.dir) /src/main/java) .setIdType(IdType.ASSIGN_ID) // 雪花算法 .setSwagger2(true) .setOpen(false); // 2. 包配置 PackageConfig packageConfig new PackageConfig(); packageConfig.setParent(com.example) .setEntity(entity) .setMapper(mapper) .setService(service) .setServiceImpl(service.impl) .setController(controller); // 3. 策略配置 StrategyConfig strategy new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel) // 表名下划线转驼峰 .setColumnNaming(NamingStrategy.underline_to_camel) // 字段名下划线转驼峰 .setEntityLombokModel(true) // 启用Lombok .setRestControllerStyle(true) // Restful风格 .setInclude(t_order, t_order_item) // 需要生成的表 .setLogicDeleteFieldName(deleted) // 逻辑删除字段 .setVersionFieldName(version) // 乐观锁字段 .setTablePrefix(t_); // 去除表前缀 // 执行生成 AutoGenerator generator new AutoGenerator(); generator.setGlobalConfig(globalConfig) .setPackageInfo(packageConfig) .setStrategy(strategy) .setTemplateEngine(new VelocityTemplateEngine()); generator.execute(); } }8.2 生成代码的二次修改规范生成后的实体类建议继承统一的BaseEntity避免重复定义id、createTime、updateTime等公共字段生成的 Controller 仅用于快速验证生产环境需根据业务逻辑重构复杂查询联表查询、子查询、自定义统计仍需手写 XML不应依赖代码生成器九、常见面试题Q1MyBatis-Plus 和 MyBatis 是什么关系加分表述MyBatis-Plus 是 MyBatis 的“增强工具”而非“替代品”。它在完全兼容 MyBatis 原有功能XML 映射、动态 SQL、插件机制、事务模型的基础上通过封装通用 CRUD 接口、提供条件构造器、内置插件等能力解决了传统 MyBatis 重复编写基础 CRUD SQL 的痛点。两者可以混合使用——通用 CRUD 满足简单需求自定义 SQL 处理复杂业务。Q2LambdaQueryWrapper 和 QueryWrapper 有什么区别加分表述核心区别在于类型安全性。QueryWrapper 通过字符串硬编码字段名当实体类字段名重构时编译阶段不会报错只有在运行时才会因 SQL 执行失败而暴露问题。而 LambdaQueryWrapper 借助 Java 8 的 Lambda 表达式的序列化机制反解析方法引用以User::getName替代name彻底规避字段名重构引发的运行时异常并享受 IDE 智能提示和编译期校验。生产环境强烈推荐使用 LambdaQueryWrapper。Q3分页插件是如何实现的加分表述MyBatis-Plus 的分页插件PaginationInnerInterceptor深度集成 MyBatis 插件体系通过拦截StatementHandler#prepare方法在 SQL 解析阶段根据数据库方言自动注入LIMIT/OFFSET或ROWNUM/ROW_NUMBER()子句。同时分页插件会将 total 总数查询拆分为独立的 COUNT 语句并支持自定义 COUNT 优化器如将COUNT(*)优化为COUNT(id)避免全表扫描实现真正的物理分页比传统 RowBounds 内存分页减少 70% 内存占用。Q4如何处理批量操作的性能问题加分表述传统方式在循环中调用save会导致多次网络往返和频繁的数据库事务提交性能极差。MP 提供了基于 JDBC BatchUpdate 的批量操作方法saveBatch和updateBatchById通过将多条 SQL 合并为一次批处理提交减少网络 IO 和数据库解析开销。实测在 1000 条数据插入场景下批量操作耗时从约 4200ms 降至约 850ms性能提升近 5 倍。Q5事务注解为什么可能会失效加分表述Spring 的Transactional基于 AOP 代理实现。常见失效场景包括第一同类中方法自调用this.method()此时调用的是原始对象而非代理对象AOP 切面不生效第二方法非 public第三异常类型配置不当默认只回滚RuntimeException和Error第四事务方法内进行网络 IO 操作虽然注解本身不失效但会导致数据库连接长期不释放从架构层面破坏事务设计。解决方案是使用Lazy注入自身代理对象进行调用或将相关逻辑抽取到不同 Service 类中。十、总结MyBatis-Plus 的开发规范本质上是在回答三个核心问题怎么写更安全怎么写更高效怎么写更易维护本文从架构分层、实体建模、CRUD 增强、高级功能、性能优化、事务管理、代码编写和代码生成等八个维度系统梳理了 MP 开发的规范要点。核心原则可概括为安全第一使用 LambdaQueryWrapper 替代字符串拼接配置BlockAttackInnerInterceptor防止全表更新性能优先使用批量操作替代循环单条操作合理配置分页参数避免事务内混入网络 IO规范统一借助代码生成器统一团队编码风格通过自定义 MetaObjectHandler 统一处理公共字段遵循这些规范团队不仅能写出高质量的数据访问层代码更能从根本上避免大部分常见的性能和安全隐患。

更多文章