MyBatis-Plus 通用 Service 与常用注解

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 —— 逻辑删除)

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,适用于 LongIntegerString 类型。默认策略,使用雪花算法
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,自动过滤已逻辑删除的数据。