1. 问题描述
当使用 MyBatis-Plus 的逻辑删除功能时,插入数据后发现逻辑删除字段(如 deleted
)未被自动设置为逻辑未删除状态(如 0
或 N
),导致以下问题:
- 数据库中该字段值为
NULL
或其他无效值。 - 查询时因逻辑删除过滤条件(如
deleted = 0
)导致数据不可见。 - 更新或删除操作因字段值不匹配而失败。
2. 逻辑删除的工作原理
MyBatis-Plus 的逻辑删除功能通过 @TableLogic
注解实现,其核心逻辑如下:
- 插入(INSERT):不自动处理逻辑删除字段的值,需手动设置或通过其他方式赋值。
- 查询(SELECT):自动追加
deleted = 逻辑未删除值
条件,过滤已删除数据。 - 更新(UPDATE):仅允许更新未删除的记录(添加
deleted = 逻辑未删除值
条件)。 - 物理删除(DELETE):转换为更新操作,将
deleted
字段设为逻辑删除值(如1
)。
示例:
-- 物理删除转换为逻辑删除
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0;
3. 问题成因分析
插入时逻辑删除字段未生效的原因在于:
- MyBatis-Plus 未强制插入逻辑删除字段的默认值,仅在查询、更新、删除时自动处理。
- 字段未设置默认值,且插入时未显式赋值,导致字段值为
NULL
或数据库默认值(如0
可能被覆盖)。
4. 解决方案与实现
以下是三种解决方案的详细实现步骤:
方案一:数据库设置默认值
步骤:
- 在数据库表中为逻辑删除字段设置默认值(如
0
表示未删除)。 - 插入时无需手动赋值,字段自动填充默认值。
示例(MySQL):
-- 修改字段默认值
ALTER TABLE user
MODIFY COLUMN deleted INT DEFAULT 0 COMMENT '逻辑删除标记(0-未删除,1-已删除)';
优势:
- 简单直接,无需代码干预。
- 适用于所有插入操作。
注意事项:
- 确保字段类型与默认值匹配(如
INT
对应0
,VARCHAR
对应'N'
)。
方案二:手动赋值
步骤:
- 在插入数据前,显式设置逻辑删除字段的值为逻辑未删除状态。
示例代码:
// 实体类
@Data
public class User {
private Long id;
@TableLogic
private Integer deleted; // 逻辑删除字段
private String name;
}
// 插入操作
User user = new User();
user.setName("Test");
user.setDeleted(0); // 手动设置为未删除状态
userMapper.insert(user);
优势:
- 直接控制字段值,灵活性高。
缺点:
- 需在每处插入逻辑中显式赋值,容易遗漏。
方案三:MyBatis-Plus 自动填充功能
步骤:
- 定义自动填充处理器:通过
MetaObjectHandler
实现字段的自动填充。 - 配置实体类:使用
@TableField(fill = FieldFill.INSERT)
标注字段。
代码实现:
// 自动填充处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 自动填充逻辑删除字段为未删除状态
this.setFieldValByName("deleted", 0, metaObject);
}
}
// 实体类配置
@Data
public class User {
private Long id;
@TableLogic
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private Integer deleted;
private String name;
}
优势:
- 自动化处理,无需手动赋值。
- 符合 MyBatis-Plus 的设计模式,代码规范。
注意事项:
- 确保
MetaObjectHandler
被 Spring 容器管理(添加@Component
)。
5. 全局配置与扩展
全局配置(application.yml
):
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段名
logic-not-delete-value: 0 # 逻辑未删除值(如 0)
logic-delete-value: 1 # 逻辑删除值(如 1)
自定义逻辑删除值:
// 实体类字段配置
@TableLogic(value = "0", delval = "1")
private Integer deleted;
6. 注意事项与最佳实践
-
字段类型匹配:
- 确保数据库字段类型与实体类字段类型一致(如
INT
对应Integer
)。 - 避免因类型不匹配导致的
NULL
或转换异常。
- 确保数据库字段类型与实体类字段类型一致(如
-
默认值冲突:
- 如果数据库字段已设置默认值,需确保与 MyBatis-Plus 配置一致。
- 推荐:移除数据库默认值,完全依赖 MyBatis-Plus 自动填充或手动赋值。
-
查询过滤逻辑:
- 逻辑删除字段的值必须与全局配置或注解定义的
logic-not-delete-value
匹配,否则查询结果会过滤掉未删除的数据。
- 逻辑删除字段的值必须与全局配置或注解定义的
-
代码规范:
- 使用
@TableField
和@TableLogic
注解明确字段逻辑,避免隐式依赖。 - 自动填充功能优先于手动赋值,避免冲突。
- 使用
7. 完整代码示例
实体类(User.java)
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(fill = FieldFill.INSERT)
@TableLogic(value = "0", delval = "1")
private Integer deleted;
private String name;
private LocalDateTime createTime;
}
自动填充处理器(MyMetaObjectHandler.java)
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
}
数据库表结构(DDL)
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
deleted INT DEFAULT 0 COMMENT '逻辑删除标记(0-未删除,1-已删除)',
create_time DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
8. 总结
- 插入时逻辑删除字段的值必须显式设置,否则可能导致数据不可见或状态异常。
- 推荐方案:
- 数据库默认值:简单直接,适用于所有插入场景。
- MyBatis-Plus 自动填充:自动化且规范,推荐用于复杂项目。
- 核心原则:理解 MyBatis-Plus 的逻辑删除设计,避免因字段初始值错误导致的业务问题。
9. 官方文档参考
如需进一步了解 MyBatis-Plus 逻辑删除功能的详细配置与使用方法,可查阅以下官方文档:
MyBatis-Plus 逻辑删除官方指南
https://www.baomidou.com/guides/logic-delete/