IService<T> 是 MyBatis-Plus 提供的服务层通用接口,封装了常用的 CRUD 操作,配合 ServiceImpl<M,T> 实现类,可极大简化业务开发,避免重复编写基础增删改查代码。


一、核心概念

1. 什么是 IService<T>?

  • IService<T> 是 MyBatis-Plus 定义的服务层通用接口,位于 com.baomidou.mybatisplus.extension.service 包中。
  • 它提供了丰富的 CRUD 方法,基于 BaseMapper<T> 实现,无需手动编写 SQL。
  • 开发者只需继承 IService<User>,MyBatis-Plus 会通过 ServiceImpl<M,T> 提供默认实现。

2. 核心优势

  • ✅ 减少样板代码:无需重复写 insert, selectById 等方法。
  • ✅ 链式调用:支持 lambdaQuery()、lambdaUpdate() 等便捷构造器。
  • ✅ 事务支持:批量操作(如 saveBatch)默认开启事务。
  • ✅ 扩展性强:可自定义通用方法,注入到全局。

3. 继承关系

public interface IService<T> { ... }

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> { ... }

二、操作步骤(非常详细)

步骤 1:准备实体类与 Mapper

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private LocalDateTime createTime;
    private Integer status;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {}

步骤 2:创建 Service 接口继承 IService<User>

public interface UserService extends IService<User> {
    // 可在此定义业务方法,如:
    // List<User> getUsersByDept(Long deptId);
}

步骤 3:创建 Service 实现类继承 ServiceImpl<UserMapper, User>

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    // 无需实现 IService 方法,已由父类提供
    // 可在此重写或扩展方法
}

步骤 4:在 Controller 或其他 Service 中注入并使用

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

步骤 5:核心方法使用详解

1. save(T entity) —— 保存单条记录

@PostMapping
public R<Boolean> saveUser(@RequestBody User user) {
    boolean result = userService.save(user);
    return R.ok(result);
}
  • 功能:插入一条记录。
  • 自动填充:若字段有 @TableField(fill = FieldFill.INSERT),会自动填充(需配置 MetaObjectHandler)。
  • ID 生成:根据 @TableId 类型自动处理(如 IdType.AUTO, IdType.ASSIGN_ID)。

2. saveOrUpdate(T entity) —— 保存或更新

@PutMapping
public R<Boolean> saveOrUpdateUser(@RequestBody User user) {
    boolean result = userService.saveOrUpdate(user);
    return R.ok(result);
}
  • 判断逻辑:
    • 若 entity.id != null 且数据库存在该 ID,则执行 UPDATE。
    • 否则执行 INSERT。
  • 注意:仅根据主键判断,不判断唯一索引。

3. saveBatch(Collection<T> entityList) —— 批量保存

@PostMapping("/batch")
public R<Boolean> saveUsers(@RequestBody List<User> users) {
    boolean result = userService.saveBatch(users, 100); // 每 100 条提交一次
    return R.ok(result);
}
  • 性能优化:使用 JDBC batch,减少数据库交互次数。
  • 事务控制:默认开启事务,任一条失败则回滚(可通过 throwEx = false 关闭)。

4. remove(Wrapper<T> queryWrapper) —— 根据条件删除

@DeleteMapping("/by-name")
public R<Boolean> removeByName(@RequestParam String name) {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name", name);
    boolean result = userService.remove(wrapper);
    return R.ok(result);
}
  • 等价于:DELETE FROM user WHERE name = ?
  • 逻辑删除:若字段标注 @TableLogic,则执行 UPDATE 标记删除。

5. list(Wrapper<T> queryWrapper) —— 查询列表

@GetMapping
public R<List<User>> listUsers() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("status", 1).orderByDesc("create_time");
    List<User> users = userService.list(wrapper);
    return R.ok(users);
}
  • 返回值:List<T>,空列表不会返回 null。

6. page(IPage<T> page, Wrapper<T> queryWrapper) —— 分页查询

@GetMapping("/page")
public R<IPage<User>> pageUsers(Page<User> page, 
                               @RequestParam(required = false) String name) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.like(StringUtils.hasText(name), User::getName, name);
    
    IPage<User> result = userService.page(page, wrapper);
    return R.ok(result);
}
  • 参数:
    • page:Page<T> 对象,包含当前页、页大小。
    • queryWrapper:查询条件。
  • 返回值:IPage<T>,包含总记录数、当前页数据等。

7. count(Wrapper<T> queryWrapper) —— 统计数量

@GetMapping("/count")
public R<Long> countUsers(@RequestParam Integer status) {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("status", status);
    long count = userService.count(wrapper);
    return R.ok(count);
}
  • 性能提示:生成 SELECT COUNT(*),建议对 WHERE 字段建索引。

8. update(T entity, Wrapper<T> updateWrapper) —— 条件更新

@PutMapping("/status")
public R<Boolean> updateStatus(@RequestParam Integer status, 
                              @RequestParam Long id) {
    User user = new User();
    user.setStatus(status); // 要更新的字段
    
    LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
    wrapper.eq(User::getId, id);
    
    boolean result = userService.update(user, wrapper);
    return R.ok(result);
}
  • 注意:entity 中 null 值字段不会更新(除非配置 fieldStrategy)。
  • 推荐:使用 LambdaUpdateWrapper 避免字段名硬编码。

9. getOne(Wrapper<T> queryWrapper) —— 查询单条记录

@GetMapping("/one")
public R<User> getOneUser(@RequestParam String email) {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("email", email);
    User user = userService.getOne(wrapper);
    return R.ok(user);
}
  • 注意:若查询到多条记录,会抛出 TooManyResultsException。
  • 安全用法:加 last("LIMIT 1") 或使用 getOne(wrapper, false) 忽略异常。

三、常见错误与解决方案

错误现象 原因 解决方案
save 报主键冲突 @TableId 类型配置错误 使用 IdType.AUTO 或 ASSIGN_ID
update 不更新 null 值 默认策略忽略 null 使用 updateWrapper.set("column", null)
page 不分页,查全表 未传 Page 对象 确保第一个参数是 Page<T>
saveBatch 性能差 未设置 batchSize 设置合理 batchSize(如 100~500)
remove 执行 UPDATE 而非 DELETE 启用了逻辑删除 检查 @TableLogic 注解
getOne 抛异常 查询到多条数据 加 LIMIT 1 或使用 false 参数忽略异常

四、注意事项

  1. 主键策略必须明确
    使用 @TableId(type = IdType.AUTO) 或分布式 ID(如 IdType.ASSIGN_ID)。

  2. 逻辑删除字段需标注 @TableLogic
    否则 remove 会物理删除。

  3. 批量操作注意内存
    大数据量 saveBatch 建议分批处理,避免 OOM。

  4. update 方法不更新 null 值
    如需更新 null,使用 updateWrapper.set("col", null)。

  5. IService 方法默认无事务
    但 saveBatch、remove 等内部方法有事务。跨方法调用需自行加 @Transactional。

  6. 避免在 IService 中写复杂业务逻辑
    复杂逻辑应在 Service 实现类中重写方法。


五、使用技巧

技巧 1:链式查询(推荐)

// 查询状态为 1 的用户,按年龄排序
List<User> users = userService.lambdaQuery()
    .eq(User::getStatus, 1)
    .orderByDesc(User::getAge)
    .list();

// 更新邮箱
userService.lambdaUpdate()
    .eq(User::getId, 1L)
    .set(User::getEmail, "new@example.com")
    .update();

// 删除
userService.lambdaUpdate()
    .eq(User::getStatus, 0)
    .remove();

✅ 强烈推荐使用 lambdaQuery() / lambdaUpdate(),类型安全,代码简洁。

技巧 2:条件动态拼接

userService.lambdaQuery()
    .eq(age != null, User::getAge, age)
    .like(StringUtils.hasText(name), User::getName, name)
    .list();

技巧 3:自定义通用方法

// 1. 扩展 IService
public interface CustomIService<T> extends IService<T> {
    List<T> findAll();
}

// 2. 扩展 ServiceImpl
@Service
public class CustomServiceImpl<T> extends ServiceImpl<UserMapper, User> 
    implements CustomIService<User> {
    
    @Override
    public List<User> findAll() {
        return this.list();
    }
}

六、最佳实践

实践 说明
✅ 服务层继承 IService<T> 减少重复代码
✅ 实现类继承 ServiceImpl<M,T> 获得默认实现
✅ 优先使用 lambdaQuery() 类型安全,避免硬编码
✅ 批量操作设置 batchSize 控制内存与性能平衡
✅ 分页使用 IPage + Page 标准化分页接口
✅ 复杂逻辑重写方法 在 ServiceImpl 中覆盖默认行为
✅ 合理使用 @Transactional 控制事务边界

七、性能优化

优化点 说明
批量操作 使用 saveBatch(list, batchSize) 提升插入性能
索引优化 WHERE、ORDER BY 字段必须建索引
避免 N+1 查询 不要在循环中调用 getById
分页合理 避免 OFFSET 过大,可用游标分页(WHERE id > lastId)
减少字段查询 使用 select 指定字段,避免 SELECT *
缓存热点数据 结合 Redis 缓存频繁查询结果
JDBC Batch 确保数据库连接配置 rewriteBatchedStatements=true(MySQL)

八、总结

方法 用途 注意事项
save 插入单条 主键策略
saveOrUpdate 存在更新,否则插入 仅判断主键
saveBatch 批量插入 设置 batchSize
remove 条件删除 注意逻辑删除
list 查询列表 配合 QueryWrapper
page 分页查询 传 Page 对象
count 统计数量 建议索引
update 条件更新 null 值不更新
getOne 查询单条 防止多条异常

✅ 一句话总结:
IService<T> 是 MyBatis-Plus 服务层的核心抽象,通过继承它和 ServiceImpl,开发者可快速获得一套完整、高效、可维护的 CRUD 能力,是构建 Spring Boot 应用的推荐模式。