一、核心概念

1. ServiceImpl 是什么?

ServiceImpl<M, T> 是 MyBatis-Plus 提供的 通用 Service 层实现基类,封装了常见的 CRUD 操作,开发者无需重复编写基础业务逻辑,可快速构建 Service 层。

✅ 本质:对 BaseMapper 的进一步封装,提供更贴近业务的 API。

2. 继承关系图解

                          +---------------------+
                          |    IService<T>      |  ← 定义通用方法(如 save, list, page)
                          +----------+----------+
                                     |
                                     | 实现
                                     v
                          +---------------------+
                          |  ServiceImpl<M, T>  |  ← 提供默认实现(依赖 BaseMapper)
                          +----------+----------+
                                     |
                                     | 继承(你的业务 Service)
                                     v
                   +-------------------------------+
                   |   UserServiceImpl extends    |
                   |   ServiceImpl<UserMapper, User> |
                   +-------------------------------+

3. 核心优势

优势 说明
减少模板代码 无需手动调用 mapper.insert()
链式调用支持 lambdaQuery().eq(...).list()
事务默认集成 多操作方法(如 saveBatch)自带事务
与 Wrapper 深度集成 支持 QueryWrapper, LambdaQueryWrapper
易于扩展 可在子类中重写或新增方法

二、操作步骤(超详细,适合快速上手)

步骤 1:创建实体类(Entity)

@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; // 0:禁用, 1:启用
}

步骤 2:创建 Mapper 接口(继承 BaseMapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 可在此添加自定义 SQL 方法
}

BaseMapper<User> 提供了 insert, selectById, updateById, deleteById 等基础方法。


步骤 3:创建 Service 接口(继承 IService

public interface UserService extends IService<User> {
    // 可在此定义业务方法
    List<User> getUsersByAgeRange(Integer minAge, Integer maxAge);
    void disableUser(Long id);
}

IService<User> 提供了 save, removeById, list, page 等通用方法。


步骤 4:创建 Service 实现类(继承 ServiceImpl

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    /**
     * 自定义业务方法:根据年龄范围查询用户
     */
    @Override
    public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {
        return this.lambdaQuery()
                   .ge(User::getAge, minAge)
                   .le(User::getAge, maxAge)
                   .list();
    }

    /**
     * 自定义业务方法:禁用用户(带业务逻辑)
     */
    @Override
    @Transactional
    public void disableUser(Long id) {
        User user = this.getById(id);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在:" + id);
        }
        if (user.getStatus().equals(0)) {
            log.warn("用户 {} 已被禁用", id);
            return;
        }

        user.setStatus(0);
        boolean success = this.updateById(user);
        if (success) {
            log.info("用户 {} 已成功禁用", id);
            // 可在此触发事件,如发送通知
        } else {
            throw new RuntimeException("禁用用户失败:" + id);
        }
    }

    /**
     * 批量保存用户(演示 ServiceImpl 的内置方法)
     */
    public boolean saveUsersBatch(List<User> users) {
        return this.saveBatch(users, 100); // 每批 100 条
    }
}

🔑 关键点:

  • extends ServiceImpl<UserMapper, User>:泛型 1 是 Mapper 类型,泛型 2 是实体类型
  • implements UserService:实现业务接口
  • this.lambdaQuery():调用 ServiceImpl 提供的便捷查询链

步骤 5:在 Controller 中使用 Service

@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getById(id);
    }

    @PostMapping
    public String createUser(@RequestBody User user) {
        boolean saved = userService.save(user);
        return saved ? "success" : "fail";
    }

    @GetMapping("/age")
    public List<User> getUsersByAge(@RequestParam Integer min, @RequestParam Integer max) {
        return userService.getUsersByAgeRange(min, max);
    }

    @DeleteMapping("/{id}")
    public String disable(@PathVariable Long id) {
        userService.disableUser(id);
        return "disabled";
    }
}

三、常见错误与解决方案

错误现象 原因 解决方案
NoSuchBeanDefinitionException: No qualifying bean of type 'UserService' @Service 注解缺失或未扫描 检查 @Service@ComponentScan
Mapper 为 null ServiceImpl 未正确注入 BaseMapper 确保继承 ServiceImpl<M, T> 且 M 是 Mapper 接口
saveBatch 不回滚 异常被捕获未抛出 确保在 @Transactional 方法中抛出运行时异常
lambdaQuery() 报错 MyBatis-Plus 版本过低 升级到 3.4.0+
getById 返回 null 但数据库有数据 逻辑删除字段问题 检查 @TableLogic 配置和 deleted 字段值

四、注意事项

  1. 必须添加 @Service
    否则 Spring 无法管理该 Bean。

  2. 泛型必须正确
    ServiceImpl<UserMapper, User>UserMapper 必须继承 BaseMapper<User>

  3. 事务控制

    • saveBatch, removeBatchByIds, updateBatchById 等批量方法自带事务
    • 自定义方法如需事务,必须添加 @Transactional
  4. 不要重写 ServiceImpl 的核心方法(除非必要):
    save, remove, update,避免破坏默认行为。

  5. 与自动填充配合
    ServiceImpl 调用 mapper.insert() 时会触发 MetaObjectHandler,确保配置正确。

  6. 空值处理

    • getById(null) 返回 null
    • removeById(null) 不执行删除

五、使用技巧

技巧 1:链式查询(Query Chain)

// 条件查询
List<User> users = this.lambdaQuery()
    .eq(User::getStatus, 1)
    .gt(User::getAge, 18)
    .like(User::getName, "张")
    .orderByDesc(User::getCreateTime)
    .last("LIMIT 100") // 自定义 SQL 片段(慎用)
    .list();

// 单条记录
User user = this.lambdaQuery()
    .eq(User::getEmail, "zhang@email.com")
    .one(); // 返回单条,多条抛异常

// 存在性判断
boolean exists = this.lambdaQuery()
    .eq(User::getName, "张三")
    .exists();

技巧 2:分页查询

public Page<User> getUsersPage(int pageNum, int pageSize) {
    Page<User> page = new Page<>(pageNum, pageSize);
    return this.page(page, new LambdaQueryWrapper<User>().eq(User::getStatus, 1));
}

技巧 3:获取 BaseMapper

// 在 ServiceImpl 中可通过 this.getBaseMapper() 获取
UserMapper mapper = this.getBaseMapper();
List<User> customList = mapper.selectCustomMethod(); // 调用自定义 Mapper 方法

技巧 4:条件构造器复用

private LambdaQueryWrapper<User> getActiveUserWrapper() {
    return new LambdaQueryWrapper<User>().eq(User::getStatus, 1);
}

public long countActiveUsers() {
    return this.count(getActiveUserWrapper());
}

public List<User> listActiveUsers() {
    return this.list(getActiveUserWrapper());
}

六、最佳实践与性能优化

✅ 最佳实践

实践 说明
接口继承 IService 保持 API 一致性,便于替换实现
实现类继承 ServiceImpl 复用通用逻辑,减少重复代码
业务方法命名清晰 disableUser, unlockAccount
复杂逻辑封装在 Service 避免 Controller 冗长
批量操作使用 saveBatch 提升性能,减少数据库交互

⚡ 性能优化建议

  1. 合理使用批量操作

    // ✅ 推荐:批量插入,每批 100~500 条
    userService.saveBatch(users, 100);
    
    // ❌ 避免:循环单条插入
    // for (User u : users) userService.save(u);
    
  2. 分页避免 COUNT(*) 全表扫描

    // 使用 MyBatis-Plus 分页插件,自动优化 COUNT 查询
    Page<User> page = userService.page(new Page<>(1, 10), 
        Wrappers.<User>lambdaQuery().eq(User::getStatus, 1));
    
  3. 避免 N+1 查询

    // ❌ 错误:循环查数据库
    // List<User> users = userService.list();
    // for (User u : users) {
    //     u.setOrders(orderService.listByUserId(u.getId()));
    // }
    
    // ✅ 正确:在 Mapper 层 JOIN 查询 或 使用缓存
    
  4. 缓存热点数据

    @Cacheable(value = "user", key = "#id")
    public User getUserWithCache(Long id) {
        return this.getById(id);
    }
    
  5. 连接池配置

    • 使用 HikariCP,maximumPoolSize 根据并发调整(建议 20~50)

七、高级用法

场景 1:重写 ServiceImpl 方法(扩展功能)

@Slf4j
public class CustomServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {

    @Override
    public boolean save(T entity) {
        log.info("准备保存: {}", entity);
        boolean result = super.save(entity);
        log.info("保存结果: {}, ID: {}", result, 
                 ((Entity)entity).getId()); // 假设实体有 getId()
        return result;
    }
}

// 使用
@Service
public class UserServiceImpl extends CustomServiceImpl<UserMapper, User> implements UserService {
    // ...
}

场景 2:结合事件发布

@Autowired
private ApplicationEventPublisher eventPublisher;

@Override
@Transactional
public boolean save(User entity) {
    boolean saved = super.save(entity);
    if (saved) {
        eventPublisher.publishEvent(new UserCreatedEvent(entity));
    }
    return saved;
}

八、验证是否生效

  1. 启动日志:查看 Spring 是否成功注入 UserServiceImpl
  2. 调用接口:测试 GET /api/user/1 是否返回数据。
  3. 数据库操作:插入数据后检查 createTime 是否由 MetaObjectHandler 自动填充。
  4. 批量测试:插入 1000 条数据,验证 saveBatch 性能远优于循环插入。