第 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.StdOutImplSQL 性能分析插件(生产关掉):
java
@Bean
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor i = new PerformanceInterceptor();
i.setMaxTime(500); // 超过 500ms 报错
return i;
}八、本章小结
| 要点 | 关键 |
|---|---|
| 自动填充 | MetaObjectHandler |
| 多租户 | TenantLineInnerInterceptor |
| 批处理 | rewriteBatchedStatements=true |
| 分表 | DynamicTableNameInnerInterceptor |
| 雪花 ID | IdType.ASSIGN_ID |
动手练习
- 给所有表加
create_time/update_time自动填充 - 用多租户拦截器,实现租户隔离
下一章:第 51 章:数据源与事务 →