MyBatis-Plus(MP)除了封装常见的 CRUD 操作,还提供了一些高级功能,进一步简化复杂场景下的开发工作。本文将逐一讲解 逻辑删除、自动填充、多表关联查询的原理与使用方式,让你快速掌握这些技巧!
逻辑删除是指在数据库中不直接删除记录,而是通过标记(如 is_deleted
字段)表示数据是否有效。
逻辑删除是指在数据库中不直接删除记录,而是通过标记(如 is_deleted
字段)来表示数据是否有效,查询时会自动过滤掉已标记为“删除”的记录。
在数据库表中添加逻辑删除字段(如 is_deleted
):
ALTER TABLE user ADD COLUMN is_deleted TINYINT(1) DEFAULT 0 COMMENT '逻辑删除标记';
在实体类中添加逻辑删除注解:
@TableLogic
private Integer isDeleted;
全局配置逻辑删除行为(application.yml
):
mybatis-plus:
global-config:
db-config:
logic-delete-field: is_deleted
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
执行以下查询语句:
SELECT * FROM user;
假设初始数据为空:
id | name | is_deleted |
---|---|---|
User user = new User();
user.setName("Tom");
userMapper.insert(user);
执行后,生成的 SQL 为:
INSERT INTO user (name, is_deleted) VALUES ('Tom', 0);
id | name | is_deleted |
---|---|---|
1 | Tom | 0 |
userMapper.deleteById(1);
生成的 SQL 为:
UPDATE user SET is_deleted = 1 WHERE id = 1;
id | name | is_deleted |
---|---|---|
1 | Tom | 1 |
默认情况下,逻辑删除记录不会出现在查询结果中:
List<User> users = userMapper.selectList(null);
生成的 SQL 为:
SELECT id, name FROM user WHERE is_deleted = 0;
查询结果:
id | name |
---|---|
若想查询所有记录(包括逻辑删除的记录),需要自定义 SQL:
@Select("SELECT * FROM user")
List<User> selectAllUsers();
查询结果:
id | name | is_deleted |
---|---|---|
1 | Tom | 1 |
通过示例可以看出,逻辑删除通过 自动更新 is_deleted
字段 和 动态添加查询条件 实现了逻辑上的“删除”操作,同时保留了数据的可追溯性。
自动填充用于在插入或更新记录时,自动为某些字段赋值(如创建时间、更新时间)。
实现自动填充的关键是使用 @TableField
的 fill
属性,并结合 MetaObjectHandler
接口完成填充逻辑。
自动填充的核心原理在于 MyBatis-Plus 提供的元对象处理器(MetaObjectHandler
)接口。通过实现这个接口,MP 能够在插入或更新数据时自动填充指定字段。这背后实际上是一种 拦截机制:在执行插入或更新操作时,MP 会检测是否有配置的填充字段,如果有,就调用实现类的逻辑为这些字段赋值。
以下我们详细来分析一下:
INSERT
)或更新(UPDATE
)操作时,会检查实体类中是否有标注了 @TableField(fill = ...)
的字段。如果存在这些字段,则会触发自动填充逻辑。MetaObjectHandler
接口实现了这个逻辑,通过重写其中的 insertFill
和 updateFill
方法,可以定义插入或更新时如何自动填充字段。MetaObjectHandler
的类,并在执行插入或更新操作时调用相关方法。在实体类中通过 @TableField(fill = ...)
指定需要填充的字段:
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
FieldFill.INSERT
:仅在插入时填充。
FieldFill.INSERT_UPDATE
:在插入和更新时都会填充。
实现 MetaObjectHandler
接口。只需创建一个类并实现 MetaObjectHandler
,就会自动扫描并加载这个类的配置,无需额外声明。记得通过@Component
注解标注为组件,SpringBoot 才能够扫描到
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
}
strictInsertFill
和strictUpdateFill
是 MP 提供的方法,用于安全地为指定字段赋值。MP 会自动调用
insertFill
和updateFill
,而开发者无需显式调用。
插入或更新时,无需手动设置 createTime
和 updateTime
:
User user = new User();
user.setName("Tom");
userMapper.insert(user);
执行后,自动填充的 SQL 为:
INSERT INTO user (name, create_time, update_time)
VALUES ('Tom', '2025-01-01 12:00:00', '2025-01-01 12:00:00');
User user = userMapper.selectById(1);
user.setName("Jerry");
userMapper.updateById(user);
执行后,自动填充的 SQL 为:
UPDATE user
SET name = 'Jerry', update_time = '2025-01-01 12:05:00'
WHERE id = 1;
虽然 MyBatis-Plus 不直接支持多表联查,但我们可以通过以下方式实现:
假设我们有以下两张表:
用户表(user
)
字段名 | 类型 | 描述 |
---|---|---|
id | INT | 主键 |
name | VARCHAR(50) | 用户名 |
VARCHAR(50) | 邮箱 |
订单表(order
)
字段名 | 类型 | 描述 |
---|---|---|
id | INT | 主键 |
user_id | INT | 用户 ID |
order_time | DATETIME | 下单时间 |
amount | DECIMAL(10, 2) | 订单金额 |
用户实体类(User
)
@Data
public class User {
private Integer id;
private String name;
private String email;
// 关联的订单集合
private List<Order> orders;
}
订单实体类(Order
)
@Data
public class Order {
private Integer id;
private Integer userId;
private LocalDateTime orderTime;
private BigDecimal amount;
}
自定义 SQL 和返回结果映射
通过 MyBatis 的 XML 配置实现多表联查:
UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserWithOrders" resultMap="userWithOrdersMap">
SELECT
u.id AS userId,
u.name AS userName,
u.email AS userEmail,
o.id AS orderId,
o.order_time AS orderTime,
o.amount AS orderAmount
FROM user u
LEFT JOIN order o ON u.id = o.user_id
WHERE u.id = #{userId}
select>
<resultMap id="userWithOrdersMap" type="com.example.entity.User">
<id property="id" column="userId" />
<result property="name" column="userName" />
<result property="email" column="userEmail" />
<collection property="orders" ofType="com.example.entity.Order">
<id property="id" column="orderId" />
<result property="orderTime" column="orderTime" />
<result property="amount" column="orderAmount" />
collection>
resultMap>
mapper>
public interface UserMapper extends BaseMapper<User> {
User getUserWithOrders(@Param("userId") Integer userId);
}
Service 层调用
@Autowired
private UserMapper userMapper;
public User getUserWithOrders(Integer userId) {
return userMapper.getUserWithOrders(userId);
}
测试用例
@Test
public void testGetUserWithOrders() {
User user = userMapper.getUserWithOrders(1);
System.out.println("用户信息:" + user.getName());
System.out.println("订单信息:");
user.getOrders().forEach(order -> {
System.out.println("订单 ID:" + order.getId());
System.out.println("订单金额:" + order.getAmount());
});
}
假设 user
表中有以下记录:
id | name | |
---|---|---|
1 | 张三 | [email protected] |
order
表中有以下记录:
id | user_id | order_time | amount |
---|---|---|---|
1 | 1 | 2025-01-01 12:00:00 | 100.00 |
2 | 1 | 2025-01-01 15:00:00 | 200.00 |
运行测试后输出:
用户信息:Alice
订单信息:
订单 ID:1
订单金额:100.00
订单 ID:2
订单金额:200.00