{T}

代码生成与性能优化

MyBatis-Plus 提供代码生成器快速生成 Entity、Mapper、Service、Controller 等代码,减少重复劳动。本章还介绍批量操作优化、SQL 性能分析和常见问题排查。

代码生成器

添加依赖

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.12</version>
</dependency>

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

FastAutoGenerator 配置(推荐)

MyBatis-Plus 3.5.1+ 提供了 FastAutoGenerator,比旧版 AutoGenerator 更简洁:

java
package com.xiaoye.mybatisplus.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;

import java.util.Collections;

public class CodeGenerator {

    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root1234";
        String moduleName = "system";
        String mapperLocation = "/src/main/resources/mapper/" + moduleName;

        FastAutoGenerator.create(url, username, password)
            .globalConfig(builder -> {
                builder.author("xiaoye")
                       .outputDir(System.getProperty("user.dir") + "/src/main/java")
                       .enableSwagger();  // 生成 Swagger 注解
            })
            .packageConfig(builder -> {
                builder.parent("com.xiaoye.mybatisplus")
                       .moduleName(moduleName)
                       .pathInfo(Collections.singletonMap(
                           OutputFile.xml,
                           System.getProperty("user.dir") + mapperLocation
                       ));
            })
            .strategyConfig(builder -> {
                builder.addInclude("user")           // 指定要生成的表
                       .addTablePrefix("t_", "sys_") // 过滤表前缀
                       .entityBuilder()
                           .enableLombok()             // 使用 Lombok
                           .enableTableFieldAnnotation() // 生成 @TableField 注解
                       .controllerBuilder()
                           .enableRestStyle()           // 生成 @RestController
                       .mapperBuilder()
                           .enableMapperAnnotation();   // 生成 @Mapper 注解
            })
            .templateEngine(new VelocityTemplateEngine())
            .execute();
    }
}

生成的文件结构

code
src/main/java/com/xiaoye/mp/system/
├── entity/
│   └── User.java
├── mapper/
│   └── UserMapper.java
├── service/
│   ├── UserService.java
│   └── impl/
│       └── UserServiceImpl.java
└── controller/
    └── UserController.java

src/main/resources/mapper/system/
└── UserMapper.xml
代码生成器使用建议
  1. 开发初期使用:快速搭建项目骨架,生成基础 CRUD 代码
  2. 不要反复重新生成:生成后的代码应该由开发者维护,反复重新生成会覆盖自定义逻辑
  3. 生产环境删除生成器依赖:代码生成器是开发工具,不应出现在生产环境的依赖中
  4. 配合自定义模板:团队可以根据项目规范自定义生成模板

批量操作优化

Service 层批量方法

java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService {

    // 批量插入
    @Transactional(rollbackFor = Exception.class)
    public void batchInsertUsers(List<User> users) {
        // saveBatch 默认每批 1000 条,可以自定义批次大小
        saveBatch(users, 500);  // 每批 500 条
    }

    // 批量更新
    @Transactional(rollbackFor = Exception.class)
    public void batchUpdateUsers(List<User> users) {
        updateBatchById(users, 500);
    }

    // 批量插入或更新(根据 ID 是否存在自动判断)
    @Transactional(rollbackFor = Exception.class)
    public void batchSaveOrUpdate(List<User> users) {
        saveOrUpdateBatch(users, 500);
    }
}

批量操作的性能考量

java
// × 性能差:逐条插入(1000 条 = 1000 次 SQL 执行)
for (User user : userList) {
    userMapper.insert(user);
}

// √ 性能好:批量插入(1000 条 = 2 批次 × 500 条/批)
saveBatch(userList, 500);
批量操作的注意事项
  1. 批次大小不宜过大:推荐 500-1000 条/批,过大可能导致 SQL 超长或内存溢出
  2. 必须搭配事务:批量操作应该在事务中执行,否则部分失败会导致数据不一致
  3. 批量更新只更新非 null 字段updateBatchById 默认只更新实体中非 null 的字段
  4. MySQL 批量插入优化:确保 MySQL 连接参数包含 rewriteBatchedStatements=true,可以将多条 INSERT 合并为一条

MySQL 批量插入配置

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?rewriteBatchedStatements=true

这个参数让 MySQL JDBC 驱动将 INSERT INTO ... VALUES (...); INSERT INTO ... VALUES (...); 合并为 INSERT INTO ... VALUES (...), (...), (...);,大幅提升批量插入性能。

SQL 性能分析

开发环境 SQL 日志

yaml
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印 SQL 和参数
生产环境必须关闭

StdOutImpl 会将每条 SQL 打印到控制台,生产环境必须关闭,否则会产生大量日志影响性能。生产环境使用 Slf4jImplNologgingImpl

P6Spy 性能分析

P6Spy 可以拦截 SQL 并记录执行时间,方便定位慢查询:

xml
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>
yaml
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai

spy.properties 配置:

properties
modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
databaseDialectDateFormat=yyyy-MM-dd HH:mm:ss
outagedetection=true
outagedetectioninterval=1   # 执行时间超过1秒的SQL会被标记
P6Spy 仅用于开发/测试环境

P6Spy 会增加 SQL 执行开销,生产环境不应使用。定位慢查询应该使用 MySQL 的慢查询日志和 EXPLAIN

常见问题与排障

问题一:字段映射失败

现象:数据库字段 user_name 无法映射到实体类属性 userName

原因:未开启驼峰命名转换

解决

yaml
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true

问题二:主键生成异常

现象:使用 ASSIGN_ID 策略时,ID 过长(19位数字)

原因:实体类主键类型使用了 Integer 而非 Long

解决:雪花算法生成的 ID 是 Long 型(最大 19 位),实体类必须使用 LongString

java
// × 错误:Integer 无法承载雪花算法生成的 ID
@TableId(type = IdType.ASSIGN_ID)
private Integer id;

// √ 正确:使用 Long
@TableId(type = IdType.ASSIGN_ID)
private Long id;

问题三:分页不生效

现象selectPage 返回全部数据而非分页结果

原因:未配置分页插件

解决

java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

问题四:逻辑删除不生效

现象:逻辑删除后仍能查询到数据

原因:缺少 @TableLogic 注解或全局配置

解决

java
// 方式一:注解配置(推荐)
@TableLogic
private Integer deleted;

// 方式二:全局配置
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

问题五:批量操作报错 StackOverflow

现象:批量插入/更新大量数据时出现 StackOverflowError

原因:一次性操作数据量过大

解决

  • 降低批次大小(500 条/批)
  • 分批处理,每批单独提交
  • 确保 MySQL 连接参数包含 rewriteBatchedStatements=true

最佳实践总结

领域核心原则
代码生成初期使用、后期维护、不反复重新生成
批量操作控制批次大小、搭配事务、开启 rewriteBatchedStatements
SQL 分析开发环境用日志/P6Spy、生产环境用慢查询日志 + EXPLAIN
问题排查检查插件配置、注解配置、数据类型匹配

下一步:回到 MyBatis-Plus 总览 回顾完整知识体系,或结合 Spring Boot 总览 将 MyBatis-Plus 集成到实际项目中。

版本差异(旧版 3.5.5 → 3.5.x)

特性旧版(3.5.5 时期)当前 3.5.x(如 3.5.12)
代码生成器AutoGenerator(旧)/ FastAutoGenerator(3.5.1+)FastAutoGenerator 为主推,旧版 AutoGenerator 不再更新
批量操作saveBatch/updateBatchById用法不变;搭配 rewriteBatchedStatements 优化原则不变
SQL 分析日志/P6Spy/慢查询不变;3.5.x 内置 SQL 解析继续基于 JSqlParser
虚拟线程无特殊处理3.5.9+ 优化虚拟线程环境兼容

代码生成器部分:从 3.5.5 升级到 3.5.12 时,若仍使用旧版 AutoGenerator,建议迁移到 FastAutoGenerator(API 更简洁且持续维护)。