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);
}
  • 参数
    • pagePage<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);
}
  • 注意entitynull 值字段不会更新(除非配置 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.AUTOASSIGN_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 方法默认无事务
    saveBatchremove 等内部方法有事务。跨方法调用需自行加 @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) 提升插入性能
索引优化 WHEREORDER 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 应用的推荐模式。