相信很多刚用 MyBatis-Plus(MP)的新手,都会被 IService、ServiceImpl、自定义 Service 接口/实现类 这一套层级搞懵,尤其是分不清接口和实现类的关系、各自的作用。结合自己的学习梳理,把这部分核心逻辑讲透,新手看完直接上手无压力。
一、核心层级关系(重中之重)
先上一张极简层级图,一眼看懂依赖关系:
IService(MP 自带接口) ← 自定义 Service 接口(如 BmSealRecordService)
ServiceImpl(MP 自带实现类) ← 自定义 Service 实现类(如 BmSealRecordServiceImpl)
对应的代码结构(以印章记录模块为例):
// 1. MP 自带接口(定义方法名,无实现)
public interface IService {
boolean save(T entity);
boolean removeById(Serializable id);
T getById(Serializable id);
// 更多CRUD方法声明...
}
// 2. 自定义 Service 接口(继承IService,复用方法名)
public interface BmSealRecordService extends IService {
// 可新增自定义业务方法(可选)
}
// 3. MP 自带实现类(实现IService,提供方法代码)
public class ServiceImpl<M extends BaseMapper, T> implements IService {
// 实现了IService所有方法,底层调用BaseMapper操作数据库
@Override
public boolean save(T entity) {
return baseMapper.insert(entity) > 0;
}
// 更多方法实现...
}
// 4. 自定义 Service 实现类(核心)
@Service
public class BmSealRecordServiceImpl
extends ServiceImpl<BmSealRecordMapper, BmSealRecord>
implements BmSealRecordService {
// 继承ServiceImpl,拿到所有方法实现;实现自定义接口,对外暴露
}
二、各组件核心作用(通俗解读)
- IService(MP 自带接口)
- 本质:方法声明的集合,只定义 CRUD 方法的名称、参数、返回值,没有任何代码实现。
- 作用:制定通用 CRUD 规范,统一所有 Service 接口的方法名,避免混乱。
- 核心:提供 save、removeById、getById、list 等几十种常用 CRUD 方法的"规矩"。
- 自定义 Service 接口(如 BmSealRecordService)
-
本质:继承 IService,将 MP 通用的方法名"复制"到自己的接口中,成为专属业务接口。
-
作用:
- 对外暴露业务方法,供 Controller 调用(遵循 Spring 面向接口编程规范)。
- 可扩展自定义业务方法(如 seal() 盖章逻辑、checkSealCount() 印章次数校验)。
-
关键:无需重新写 CRUD 方法名,继承 IService 即可复用,减少重复代码。
- ServiceImpl(MP 自带实现类)
- 本质:IService 的实现类,已经写好了所有 IService 方法的具体代码。
- 作用:封装了 CRUD 的底层逻辑,底层调用 BaseMapper(Mapper 接口)操作数据库,我们无需自己写 SQL。
- 核心:继承它,就相当于"白嫖"了所有 CRUD 方法的实现,不用自己手写增删改查代码。
- 自定义 Service 实现类(如 BmSealRecordServiceImpl)
-
本质:同时继承 ServiceImpl(拿方法实现)、实现自定义 Service 接口(守规范)。
-
作用:
- 继承 ServiceImpl:获得所有 CRUD 方法的实现代码,无需自己写。
- 实现自定义 Service 接口:对外承诺"我实现了这些方法",让 Spring 能识别并注入,供 Controller 调用。
-
关键:可重写 IService 方法,添加自己的业务逻辑(如删除时记录日志、新增时自动填充字段)。
三、核心疑问解答(新手高频)
疑问1:IService 的方法和 ServiceImpl 的方法是什么关系?
一一对应,标准的「接口 ↔ 实现类」关系:
-
IService 定义"方法名"(规矩);
-
ServiceImpl 实现"方法代码"(干活);
-
自定义实现类继承 ServiceImpl,就拿到了"规矩+干活"的完整能力。
疑问2:为什么非要自定义 Service 接口?直接用 IService 不行吗?
不行,核心原因2点:
-
Controller 不能直接注入 IService(IService 是泛型,Spring 无法确定要注入哪个实体的实现);
-
自定义接口可扩展自己的业务方法,而 IService 只有通用 CRUD,满足不了实际业务需求。
疑问3:为什么自定义实现类要同时继承 ServiceImpl 和实现自定义接口?
Java 规定"单继承、多实现",MP 正是利用这一点设计:
-
继承 ServiceImpl:快速获得 CRUD 方法实现,省掉90%的重复代码;
-
实现自定义接口:遵循面向接口编程规范,供 Controller 注入调用,降低耦合。
疑问4:如何重写 IService 的方法(添加自定义逻辑)?
直接在自定义 Service 实现类中用 @Override 覆盖即可,示例(重写 removeById 加日志):
@Service
public class BmSealRecordServiceImpl
extends ServiceImpl<BmSealRecordMapper, BmSealRecord>
implements BmSealRecordService {
// 重写IService的removeById方法
@Override
public boolean removeById(Serializable id) {
// 1. 自定义业务逻辑(如记录删除日志)
System.out.println("删除印章记录,ID:" + id);
// 2. 调用父类(ServiceImpl)的原有逻辑(底层软删除)
return super.removeById(id);
}
}
说明:加 super.方法名() 会保留 MP 原有逻辑,不加则完全自定义实现。
四、Controller 调用逻辑(闭环)
Controller 只需要注入「自定义 Service 接口」,Spring 会自动注入它的实现类(自定义 ServiceImpl),调用方法时,实际执行的是 ServiceImpl 里的代码(或你重写后的逻辑)。
@RestController
@RequestMapping("/sealRecord")
public class BmSealRecordController {
// 注入自定义Service接口(不是实现类)
@Resource
private BmSealRecordService bmSealRecordService;
// 调用的是ServiceImpl里的实现(或重写后的逻辑)
@DeleteMapping("/del/{id}")
public Boolean del(@PathVariable Long id) {
return bmSealRecordService.removeById(id);
}
}
五、终极总结(新手必背)
- IService:定规矩(方法名),无实现;
- ServiceImpl:守规矩(实现方法),干实事;
- 自定义 Service 接口:继承规矩,可加新规矩;
- 自定义 ServiceImpl:继承实干家,遵守自己的规矩;
- Controller:找自己的规矩(自定义接口),调用实干家(实现类)的方法。
这套设计的核心目的:减少重复 CRUD 代码,遵循面向接口编程规范,方便扩展和维护,这也是 MP 能大幅提高开发效率的关键之一。