Skip to content
第 50 章 后端 ⏱ 10 分钟阅读

第 50 章:MyBatis Plus 高级 ​

学习目标 ​

  • 用 MetaObjectHandler 自动填充字段
  • 学会多租户隔离
  • 掌握批处理、动态表名

一、自动填充(创建时间 / 更新时间) ​

场景:每张表都有 create_time / update_time / create_by,手动赋值累。

java
@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, SecurityUtils.getCurrentUserId());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, "updateBy", Long.class, SecurityUtils.getCurrentUserId());
    }
}
java
@Data
public class User {
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
FieldFill时机
INSERT插入时
UPDATE更新时
INSERT_UPDATE两者都

⚠️ 坑 1:字段名要和 strictInsertFill 的第二个参数一致,否则不生效不报错。

二、多租户 ​

场景:SaaS 系统,每个用户数据要隔离。加 WHERE tenant_id = ?。

java
@Component
public class MyTenantLineHandler implements TenantLineHandler {

    @Override
    public Expression getTenantId() {
        return new LongValue(SecurityUtils.getCurrentTenantId());   // 当前租户
    }

    @Override
    public String getTenantIdColumn() {
        return "tenant_id";   // 数据库字段名
    }

    @Override
    public boolean ignoreTable(String tableName) {
        return List.of("sys_dict", "sys_config").contains(tableName);  // 字典表不分租户
    }
}
java
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler()));
        return interceptor;
    }
}

所有 SQL 自动加 WHERE tenant_id = ?。

⚠️ 坑 2:登录、注册、查询租户本身的 SQL 也要执行,需要 ignoreTable 排除,否则无限递归。

三、批处理(大量数据) ​

java
// 1. 批量插入(推荐)
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    for (User u : users) {
        mapper.insert(u);
    }
    sqlSession.commit();
}

// 2. MyBatis Plus 自带
userMapper.insertBatchSomeColumn(users);   // 不返回 ID,推荐

⚠️ 坑 3:JDBC 批处理要加 rewriteBatchedStatements=true,否则批处理假动作,一条一条发。

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

四、动态表名(分表) ​

场景:按月分表 order_202608,SQL 自动加月份后缀。

java
@Component
public class MyDynamicTableNameHandler implements DynamicTableNameHandler {

    @Override
    public String dynamicTableName(String sql, String tableName) {
        if ("order".equals(tableName)) {
            return "order_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
        }
        return tableName;
    }
}
java
interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor(new MyDynamicTableNameHandler()));

五、SQL 注入器(自定义通用方法) ​

java
// 注入器类(普通类,放 config 包下都行)
@MappedTypes(User.class)
public class MySqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methods = super.getMethodList(mapperClass);
        methods.add(new LogicDeleteByIdsWithFill());   // 自定义:批量逻辑删除 + 填充
        return methods;
    }
}
java
// 注册:必须写在 @Configuration 类里,MyBatis-Plus 启动时会自动用它
@Configuration
public class MybatisConfig {
    @Bean
    public ISqlInjector sqlInjector() {
        return new MySqlInjector();
    }
}

关键:注册那个 @Bean 方法必须放在 @Configuration 类里(通常叫 MybatisConfig.java)。MySqlInjector 类本身不需要任何注解,它是靠 @Bean 主动 new 出来的。

六、雪花算法 ID ​

java
@TableId(type = IdType.ASSIGN_ID)
private Long id;   // 19 位,适合分布式

雪花 ID 好处:递增,适合做主键索引(避免页分裂)。

七、性能监控 ​

yaml
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

SQL 性能分析插件(生产关掉):

java
@Bean
public PerformanceInterceptor performanceInterceptor() {
    PerformanceInterceptor i = new PerformanceInterceptor();
    i.setMaxTime(500);   // 超过 500ms 报错
    return i;
}

八、本章小结 ​

要点关键
自动填充MetaObjectHandler
多租户TenantLineInnerInterceptor
批处理rewriteBatchedStatements=true
分表DynamicTableNameInnerInterceptor
雪花 IDIdType.ASSIGN_ID

动手练习 ​

  1. 给所有表加 create_time / update_time 自动填充
  2. 用多租户拦截器,实现租户隔离

下一章:第 51 章:数据源与事务 →

本站基于 VitePress 构建 · 由 StackHub 团队维护