MyBatis-Plus 通用 Service 与常用注解
目录
- [1 通用 Service](#1 通用 Service)
- [1.1 IService 接口简介](#1.1 IService 接口简介)
- [1.2 创建 Service 接口与实现类](#1.2 创建 Service 接口与实现类)
- [1.3 测试通用 Service](#1.3 测试通用 Service)
- [1.3.1 查询记录数](#1.3.1 查询记录数)
- [1.3.2 批量插入](#1.3.2 批量插入)
- [1.3.3 修改操作](#1.3.3 修改操作)
- [1.3.4 批量删除](#1.3.4 批量删除)
- [2 常用注解](#2 常用注解)
- [2.1 @TableName ------ 表名映射](#2.1 @TableName —— 表名映射)
- [2.1.1 注解方式](#2.1.1 注解方式)
- [2.1.2 全局配置方式](#2.1.2 全局配置方式)
- [2.2 @TableId ------ 主键映射](#2.2 @TableId —— 主键映射)
- [2.2.1 value 属性:指定主键字段名](#2.2.1 value 属性:指定主键字段名)
- [2.2.2 type 属性:指定主键生成策略](#2.2.2 type 属性:指定主键生成策略)
- [2.2.3 雪花算法简介](#2.2.3 雪花算法简介)
- [2.3 @TableField ------ 字段映射](#2.3 @TableField —— 字段映射)
- [2.4 @TableLogic ------ 逻辑删除](#2.4 @TableLogic —— 逻辑删除)
- [2.1 @TableName ------ 表名映射](#2.1 @TableName —— 表名映射)
1 通用 Service
1.1 IService 接口简介
MyBatis-Plus 封装了 IService 接口,在 BaseMapper 的 CRUD 基础上做了进一步封装。Service 层的方法采用不同的前缀命名来区分 Mapper 层,避免混淆:
| 前缀 | 含义 | 示例方法 |
|---|---|---|
get |
查询单行 | getById() |
list |
查询集合 | list() |
remove |
删除 | removeById()、removeBatchByIds() |
save |
插入 | save()、saveBatch() |
update |
修改 | updateById() |
count |
计数 | count() |
page |
分页查询 | page() |
IService 接口由 ServiceImpl 类实现。如果内置方法无法满足业务需求,可以在自定义的 Service 接口中扩展方法。
1.2 创建 Service 接口与实现类
业务接口:
java
/**
* UserService 继承 IService,获得模板提供的基础功能
*/
public interface UserService extends IService<User> {
}
业务实现类:
java
/**
* ServiceImpl 实现了 IService,提供了基础功能的实现
* 若 ServiceImpl 无法满足业务需求,可在 UserService 中定义方法,并在此类中实现
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
ServiceImpl<UserMapper, User> 的两个泛型参数分别是 Mapper 接口和实体类。
1.3 测试通用 Service
1.3.1 查询记录数
java
@Autowired
private UserService userService;
@Test
public void testGetCount() {
long count = userService.count();
System.out.println("总记录数:" + count);
}
1.3.2 批量插入
java
@Test
public void testSaveBatch() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User user = new User();
user.setName("abc" + i);
user.setAge(20 + i);
users.add(user);
}
// SQL: INSERT INTO t_user (username, age) VALUES (?, ?)
userService.saveBatch(users);
}
saveBatch() 会将集合中的数据分批执行插入,是 Service 层相比 Mapper 层额外提供的便利方法。
1.3.3 修改操作
java
@Test
public void testUpdate() {
User user = new User(1778690964123090947L, "miller", 30, "miller@qq.com");
// SQL: UPDATE user SET name=?, age=?, email=? WHERE id=?
userService.updateById(user);
}
1.3.4 批量删除
java
@Test
public void testDelete() {
List<Long> ids = new ArrayList<>();
ids.add(1778690963879821313L);
ids.add(1778690964114702337L);
ids.add(1778690964114702338L);
ids.add(1778690964123090946L);
ids.add(1778690964123090947L);
// SQL: DELETE FROM user WHERE id IN (?, ?, ?, ?, ?)
userService.removeBatchByIds(ids);
}
2 常用注解
MyBatis-Plus 提供了一系列注解,用于处理实体类与数据库表之间的映射关系。核心注解汇总如下:
| 注解 | 作用位置 | 功能 |
|---|---|---|
@TableName |
类 | 指定实体类对应的表名 |
@TableId |
主键字段 | 指定主键字段名及主键生成策略 |
@TableField |
普通字段 | 指定实体属性对应的表字段名 |
@TableLogic |
逻辑删除字段 | 标记该字段为逻辑删除标志位 |
2.1 @TableName ------ 表名映射
MyBatis-Plus 默认将实体类的类名作为表名。当实体类名与表名不一致时(例如实体类为 User,表名为 tb_user),查询会报错:
Table 'mybatis_plus.user' doesn't exist
有两种解决方式。
2.1.1 注解方式
在实体类上添加 @TableName 注解,指定对应的表名:
java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2.1.2 全局配置方式
如果所有表都有统一前缀(如 tb_),可以在 application.yml 中全局配置,无需逐个添加注解:
yaml
mybatis-plus:
global-config:
db-config:
table-prefix: tb_
2.2 @TableId ------ 主键映射
MyBatis-Plus 默认将名为 id 的字段作为主键。当主键字段名不是 id(例如为 uid)时,需要使用 @TableId 注解手动指定。
2.2.1 value 属性:指定主键字段名
当实体属性名与数据库主键字段名不一致时,通过 value 属性指定数据库中的字段名:
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(value = "uid") // 实体属性为 id,数据库字段为 uid
private Long id;
private String name;
private Integer age;
private String email;
}
2.2.2 type 属性:指定主键生成策略
type 属性用于指定主键的生成策略。MyBatis-Plus 通过 IdType 枚举类定义了以下策略:
| IdType 值 | 描述 |
|---|---|
AUTO |
数据库 ID 自增。前提:表中主键字段必须设置为自增,否则插入时会报错 |
NONE |
无状态,未设置主键类型。等同于跟随全局配置,全局默认为 ASSIGN_ID |
INPUT |
插入前由用户自行设置主键值 |
ASSIGN_ID |
自动分配 ID,适用于 Long、Integer、String 类型。默认策略,使用雪花算法 |
ASSIGN_UUID |
自动分配 UUID,适用于 String 类型 |
使用自增策略的示例:
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(value = "uid", type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
也可以在 application.yml 中全局配置主键策略:
yaml
mybatis-plus:
global-config:
db-config:
table-prefix: tb_
id-type: auto
IdType 枚举源码如下:
java
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ASSIGN_ID(3),
ASSIGN_UUID(4);
}
2.2.3 雪花算法简介
雪花算法(Snowflake)是 Twitter 开源的分布式主键生成算法,能够保证不同表的主键不重复,且同一表的主键按时间有序。
产生背景: 随着数据量增长,单表可能需要进行水平分表。分表后需要一种全局唯一的 ID 生成方案,常见方案有三种:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 主键自增分段 | 按固定范围分配 ID 段,如 1~999999 放表1,1000000~1999999 放表2 | 可平滑扩充新表,原有数据不受影响 | 数据分布可能不均匀 |
| 取模 | 用 ID % 表数量 决定数据归属 |
数据分布均匀 | 扩充新表时所有数据需要重新分布 |
| 雪花算法 | 64 bit 组合:符号位(1) + 时间戳(41) + 机器ID(10) + 序列号(12) | 按时间有序,分布式环境无 ID 碰撞,效率高 | 依赖机器时钟 |
雪花算法 ID 结构(64 bit):
- 1 bit:符号位,固定为 0(正数)
- 41 bit:时间戳(毫秒级),存储当前时间与起始时间的差值,约可使用 69.73 年
- 10 bit:机器 ID(5 bit 数据中心 + 5 bit 机器 ID),支持 1024 个节点
- 12 bit:序列号,每个节点每毫秒可生成 4096 个 ID
2.3 @TableField ------ 字段映射
MyBatis-Plus 要求实体类属性名与表字段名能对应上。存在两种情况:
情况一:驼峰命名 ↔ 下划线命名(自动转换)
实体类属性使用驼峰命名(如 userName),表字段使用下划线命名(如 user_name)时,MyBatis-Plus 会自动转换,无需额外配置。
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(value = "uid")
private Long id;
private String userName; // 自动映射到表字段 user_name
private Integer age;
private String email;
}
情况二:属性名与字段名完全不一致(需手动映射)
当属性名与字段名不满足驼峰/下划线转换规则时(如属性为 userName,字段为 name),需要使用 @TableField 注解手动指定:
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(value = "uid")
private Long id;
@TableField("name") // 手动映射:属性 userName → 字段 name
private String userName;
private Integer age;
private String email;
}
2.4 @TableLogic ------ 逻辑删除
数据删除有两种方式:
| 删除方式 | 说明 |
|---|---|
| 物理删除 | 执行 DELETE 语句,数据从数据库中真实移除,不可恢复 |
| 逻辑删除 | 执行 UPDATE 语句,将删除标志字段修改为"已删除"状态,数据仍保留在表中 |
使用 @TableLogic 注解实现逻辑删除的步骤如下:
第一步:在数据表中添加逻辑删除字段
添加 is_deleted 字段,默认值为 0(0 表示未删除,1 表示已删除):
sql
ALTER TABLE tb_user ADD COLUMN is_deleted INT DEFAULT 0;
第二步:在实体类中添加对应属性并标注注解
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(value = "uid")
private Long id;
@TableField("name")
private String userName;
private Integer age;
private String email;
@TableLogic // 标记为逻辑删除字段
private Integer isDeleted;
}
第三步:测试逻辑删除
java
@Test
public void testDeleteById() {
int result = userMapper.deleteById(7L);
System.out.println("受影响行数:" + result);
}
添加 @TableLogic 注解后,调用 deleteById() 等删除方法时,MyBatis-Plus 实际执行的 SQL 会从:
sql
-- 物理删除
DELETE FROM tb_user WHERE uid = ?
变为:
sql
-- 逻辑删除
UPDATE tb_user SET is_deleted = 1 WHERE uid = ? AND is_deleted = 0
同时,后续的查询操作会自动在 WHERE 条件中追加 AND is_deleted = 0,自动过滤已逻辑删除的数据。