MyBatis-Plus 通用 Service 与常用注解
MyBatis-Plus 通用 Service 与常用注解
目录
- 1 通用 Service
- 1.1 IService 接口简介
- 1.2 创建 Service 接口与实现类
- 1.3 测试通用 Service
- 1.3.1 查询记录数
- 1.3.2 批量插入
- 1.3.3 修改操作
- 1.3.4 批量删除
- 2 常用注解
- 2.1 @TableName —— 表名映射
- 2.1.1 注解方式
- 2.1.2 全局配置方式
- 2.2 @TableId —— 主键映射
- 2.2.1 value 属性:指定主键字段名
- 2.2.2 type 属性:指定主键生成策略
- 2.2.3 雪花算法简介
- 2.3 @TableField —— 字段映射
- 2.4 @TableLogic —— 逻辑删除
- 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 接口与实现类
业务接口:
/** * UserService 继承 IService,获得模板提供的基础功能 */publicinterfaceUserServiceextendsIService<User>{}业务实现类:
/** * ServiceImpl 实现了 IService,提供了基础功能的实现 * 若 ServiceImpl 无法满足业务需求,可在 UserService 中定义方法,并在此类中实现 */@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{}ServiceImpl<UserMapper, User>的两个泛型参数分别是 Mapper 接口和实体类。
1.3 测试通用 Service
1.3.1 查询记录数
@AutowiredprivateUserServiceuserService;@TestpublicvoidtestGetCount(){longcount=userService.count();System.out.println("总记录数:"+count);}1.3.2 批量插入
@TestpublicvoidtestSaveBatch(){ArrayList<User>users=newArrayList<>();for(inti=0;i<5;i++){Useruser=newUser();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 修改操作
@TestpublicvoidtestUpdate(){Useruser=newUser(1778690964123090947L,"miller",30,"miller@qq.com");// SQL: UPDATE user SET name=?, age=?, email=? WHERE id=?userService.updateById(user);}1.3.4 批量删除
@TestpublicvoidtestDelete(){List<Long>ids=newArrayList<>();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注解,指定对应的表名:
@Data@NoArgsConstructor@AllArgsConstructor@TableName("tb_user")publicclassUser{privateLongid;privateStringname;privateIntegerage;privateStringemail;}2.1.2 全局配置方式
如果所有表都有统一前缀(如tb_),可以在application.yml中全局配置,无需逐个添加注解:
mybatis-plus:global-config:db-config:table-prefix:tb_2.2 @TableId —— 主键映射
MyBatis-Plus 默认将名为id的字段作为主键。当主键字段名不是id(例如为uid)时,需要使用@TableId注解手动指定。
2.2.1 value 属性:指定主键字段名
当实体属性名与数据库主键字段名不一致时,通过value属性指定数据库中的字段名:
@Data@NoArgsConstructor@AllArgsConstructorpublicclassUser{@TableId(value="uid")// 实体属性为 id,数据库字段为 uidprivateLongid;privateStringname;privateIntegerage;privateStringemail;}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类型 |
使用自增策略的示例:
@Data@NoArgsConstructor@AllArgsConstructorpublicclassUser{@TableId(value="uid",type=IdType.AUTO)privateLongid;privateStringname;privateIntegerage;privateStringemail;}也可以在application.yml中全局配置主键策略:
mybatis-plus:global-config:db-config:table-prefix:tb_id-type:autoIdType枚举源码如下:
publicenumIdType{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会自动转换,无需额外配置。
@Data@NoArgsConstructor@AllArgsConstructorpublicclassUser{@TableId(value="uid")privateLongid;privateStringuserName;// 自动映射到表字段 user_nameprivateIntegerage;privateStringemail;}情况二:属性名与字段名完全不一致(需手动映射)
当属性名与字段名不满足驼峰/下划线转换规则时(如属性为userName,字段为name),需要使用@TableField注解手动指定:
@Data@NoArgsConstructor@AllArgsConstructorpublicclassUser{@TableId(value="uid")privateLongid;@TableField("name")// 手动映射:属性 userName → 字段 nameprivateStringuserName;privateIntegerage;privateStringemail;}2.4 @TableLogic —— 逻辑删除
数据删除有两种方式:
| 删除方式 | 说明 |
|---|---|
| 物理删除 | 执行DELETE语句,数据从数据库中真实移除,不可恢复 |
| 逻辑删除 | 执行UPDATE语句,将删除标志字段修改为"已删除"状态,数据仍保留在表中 |
使用@TableLogic注解实现逻辑删除的步骤如下:
第一步:在数据表中添加逻辑删除字段
添加is_deleted字段,默认值为 0(0 表示未删除,1 表示已删除):
ALTERTABLEtb_userADDCOLUMNis_deletedINTDEFAULT0;第二步:在实体类中添加对应属性并标注注解
@Data@NoArgsConstructor@AllArgsConstructorpublicclassUser{@TableId(value="uid")privateLongid;@TableField("name")privateStringuserName;privateIntegerage;privateStringemail;@TableLogic// 标记为逻辑删除字段privateIntegerisDeleted;}第三步:测试逻辑删除
@TestpublicvoidtestDeleteById(){intresult=userMapper.deleteById(7L);System.out.println("受影响行数:"+result);}添加@TableLogic注解后,调用deleteById()等删除方法时,MyBatis-Plus 实际执行的 SQL 会从:
-- 物理删除DELETEFROMtb_userWHEREuid=?变为:
-- 逻辑删除UPDATEtb_userSETis_deleted=1WHEREuid=?ANDis_deleted=0同时,后续的查询操作会自动在 WHERE 条件中追加AND is_deleted = 0,自动过滤已逻辑删除的数据。
