Skip to content

MyBatis-Plus 高级特性

  涵盖防全表更新/删除、枚举处理、JSON 序列化、字段安全等高级特性。

一、防全表更新/删除

问题: 不小心执行了不带 WHERE 条件的 UPDATE 或 DELETE,导致全表被修改或清空。

java
// 危险操作!
userMapper.delete(null);  // 没有 WHERE 条件 → 全表删除
userMapper.update(user, null);  // 没有 WHERE 条件 → 全表更新

解决方案:

java
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        // 乐观锁
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

        // 防全表更新/删除(生产环境强烈建议开启)
        BlockAttackInnerInterceptor blockAttack = new BlockAttackInnerInterceptor();
        interceptor.addInnerInterceptor(blockAttack);

        return interceptor;
    }
}
java
// 开启后,不带 WHERE 条件的操作会报错
userMapper.delete(null);
// 抛异常:Prohibition of table update operation

userMapper.update(user, null);
// 抛异常:Prohibition of full table deletion

// 必须带条件
userMapper.delete(new LambdaQueryWrapper<User>().eq(User::getId, 1L));  // OK

二、枚举处理

2.1 通用枚举

java
// 定义枚举
public enum GenderEnum implements IEnum<Integer> {
    MALE(1, "男"),
    FEMALE(2, "女");

    private final int value;
    private final String desc;

    GenderEnum(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    @Override
    public Integer getValue() {
        return this.value;
    }
}
java
// 实体类
@Data
public class User {
    private Long id;
    private String name;
    private GenderEnum gender;  // 枚举字段
}
yaml
# 配置枚举包扫描
mybatis-plus:
  type-enums-package: com.example.enums

2.2 @EnumValue 注解

java
public enum GenderEnum {
    MALE(1, "男"),
    FEMALE(2, "女");

    @EnumValue  // 标记数据库存的值
    private final int code;

    @JsonValue  // 标记 JSON 序列化的值
    private final String desc;

    GenderEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

三、JSON 字段处理

java
@Data
@TableName(value = "user", autoResultMap = true)  // 必须开启 autoResultMap
public class User {

    private Long id;
    private String name;

    // JSON 字段
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> hobbies;  // 数据库存 JSON 数组:["篮球","足球"]

    @TableField(typeHandler = JacksonTypeHandler.class)
    private Map<String, Object> extra;  // 数据库存 JSON 对象:{"key":"value"}
}
java
// 使用
User user = new User();
user.setName("张三");
user.setHobbies(Arrays.asList("篮球", "足球"));
Map<String, Object> extra = new HashMap<>();
extra.put("height", 180);
extra.put("weight", 75);
user.setExtra(extra);
userMapper.insert(user);
// 数据库:hobbies = ["篮球","足球"], extra = {"height":180,"weight":75}

四、字段安全策略

java
// FieldStrategy 控制字段的 WHERE 和 SET 行为
public enum FieldStrategy {
    IGNORED,    // 忽略判断(null 也更新)
    NOT_NULL,   // 非 null 判断(默认)
    NOT_EMPTY,  // 非空判断(null 和 "" 都不更新)
    NEVER,      // 不更新
    DEFAULT     // 跟随全局
}
java
@Data
public class User {

    @TableField(strategy = FieldStrategy.NOT_NULL)  // 默认
    private String name;

    @TableField(strategy = FieldStrategy.NOT_EMPTY)  // 空字符串也不更新
    private String email;

    @TableField(strategy = FieldStrategy.IGNORED)  // null 也更新
    private Integer age;

    @TableField(updateStrategy = FieldStrategy.NEVER)  // 永不更新
    private String createBy;
}

五、自定义 SQL 注入器

java
// 自定义全局方法
public class DeleteAll extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass,
                                                  Class<?> modelClass, TableInfo tableInfo) {
        String sql = "DELETE FROM " + tableInfo.getTableName();
        String method = "deleteAll";
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addDeleteMappedStatement(mapperClass, method, sqlSource);
    }
}
java
@Component
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Configuration configuration,
                                               Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> list = super.getMethodList(configuration, mapperClass, tableInfo);
        list.add(new DeleteAll());
        return list;
    }
}

// 自定义 BaseMapper
public interface MyBaseMapper<T> extends BaseMapper<T> {
    int deleteAll();
}

六、数据权限插件

java
@Component
public class DataPermissionInterceptor extends JsqlParserSupport
        implements InnerInterceptor {

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms,
                            Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        // 获取当前用户
        LoginUser currentUser = SecurityUtils.getCurrentUser();
        if (currentUser == null || currentUser.isAdmin()) {
            return;  // 管理员不拦截
        }

        // 解析 SQL
        CCJSqlParserUtil.parse(boundSql.getSql());
        // 在 WHERE 条件中添加数据权限过滤
        // 例如:AND create_by = 'currentUserId'
    }
}

七、速记总结

高级特性速记:

  防全表更新    → BlockAttackInnerInterceptor
  枚举处理      → IEnum 接口 + @EnumValue
  JSON 字段     → JacksonTypeHandler + autoResultMap
  字段安全策略  → FieldStrategy.NOT_NULL/NOT_EMPTY
  自定义方法    → 继承 DefaultSqlInjector
  数据权限      → 自定义 InnerInterceptor

速记口诀:
  防全表更新加拦截,枚举实现 IEnum,
  JSON 字段 TypeHandler,安全策略控更新,
  自定义方法注入器,数据权限拦截里。

八、面试要点

问题答案要点
怎么防止全表更新/删除?BlockAttackInnerInterceptor 拦截不带 WHERE 的操作
枚举怎么处理?IEnum 接口 + @EnumValue 注解
JSON 字段怎么存?JacksonTypeHandler + autoResultMap = true
字段安全策略?FieldStrategy 控制 null 值是否更新