一、Service接口提供的方法

1-1、removeByIds 和 removeBatchByIds
核心区别:SQL 执行方式:
| 方法 | 生成的 SQL | 执行次数 | 性能特点 |
|---|---|---|---|
removeByIds |
DELETE FROM table WHERE id IN (?, ?, ?) |
1 次 SQL | 快,适合中小批量(< 1000) |
removeBatchByIds |
多条 DELETE FROM table WHERE id = ? |
N 次 SQL(每个 ID 一条) | 慢,但可绕过 IN 子句限制 |
示例:假设 ID 列表为 [1, 2, 3]:
removeByIds(Arrays.asList(1,2,3))
sql
DELETE FROM user WHERE id IN (1, 2, 3);
一次执行,高效。
removeBatchByIds(Arrays.asList(1,2,3))
sql
DELETE FROM user WHERE id = 1;
DELETE FROM user WHERE id = 2;
DELETE FROM user WHERE id = 3;
三次独立 SQL,效率低。
1、为什么要有 removeBatchByIds?------解决 IN 的限制
虽然 removeByIds 更高效,但在某些数据库或场景下存在限制:
- MySQL :
IN列表长度理论上可达数万,但过长会影响性能或触发max_allowed_packet限制。 - Oracle :
IN子句最多支持 1000 个元素(硬限制!超过会报错)。 - SQL Server / DB2:也有类似限制。
此时,如果你要删 1500 条 Oracle 记录:
removeByIds(list)→ ❌ 报错:ORA-01795: maximum number of expressions in a list is 1000removeBatchByIds(list)→ ✅ 虽慢但能跑通(逐条删)
💡 实际上,MP 的
removeBatchByIds内部还会做分批次提交(默认每 1000 条一批),避免事务过大。
2、补充:removeBatchByIds 的"批"不是 JDBC Batch!
注意:虽然叫 "Batch",但它不是 JDBC 的批处理(addBatch/executeBatch) ,而是指"分批循环执行单条 DELETE"。
如果想用真正的 JDBC Batch 提升性能,MP 目前不直接支持 (需自定义 SQL 或用 SqlRunner)。
3、小结
| 场景 | 推荐方法 |
|---|---|
| 一般批量删除(ID 数 < 500) | ✅ removeByIds(高效) |
| Oracle 删除 >1000 条 | ✅ removeBatchByIds(绕过 IN 限制) |
| 需要事务控制 + 大量删除 | ⚠️ 考虑分页删除 + removeByIds(如每次删 500 条) |
| 追求极致性能(万级删除) | 🔧 自定义 SQL + foreach 或物理分区删除 |
1-2、Service接口的实现

- 自定义接口需要extends IService接口;
- 自定义实现类,需要extends ServiceImpl实现类;
【对比】:
Mapper接口,没有实现类;因为MyBatis 通过 JDK 动态代理(Dynamic Proxy)在运行时自动生成了 Mapper 接口的实现类
1-3、Mybatis 动态代理,详细原理分解
1. MyBatis 的核心机制:MapperProxy
当你调用 userMapper.selectById(1) 时,实际上执行的是 MyBatis 内部生成的一个 代理对象(Proxy)。
- MyBatis 在启动时(Spring 容器初始化阶段),会扫描所有被
@Mapper注解标记的接口,或通过@MapperScan扫描的包。 - 对每个 Mapper 接口,MyBatis 使用 JDK 动态代理 创建一个实现了该接口的代理类。
- 这个代理类的逻辑由
MapperProxy类统一处理。
📌 关键类:
org.apache.ibatis.binding.MapperProxy
2. 代理做了什么?
当调用 userMapper.selectById(1) 时,流程如下:
- 调用被代理对象的方法 → 触发
MapperProxy.invoke() MapperProxy根据 方法名 + 参数类型,去 MyBatis 的 MappedStatement 注册表中查找对应的 SQL 语句- 对于 MP 的
BaseMapper方法(如selectById),MP 已经在启动时自动注册了通用 SQL。 - 对于自定义方法(如
selectByName),MyBatis 会从 XML 或@Select注解中解析 SQL。
- 对于 MP 的
- 执行 SQL,封装结果,返回。
3. MyBatis-Plus 的增强
MP 在 MyBatis 基础上进一步自动化:
BaseMapper<T>中的 CRUD 方法(如insert,deleteById,selectList等)无需你写 SQL。- MP 在应用启动时,通过 反射 + 泛型分析,自动为每个实体生成对应的通用 SQL(比如根据表名、字段名拼装)。
- 这些 SQL 被注册到 MyBatis 的
Configuration中,供MapperProxy调用。
💡 本质:MP 把"模板化 SQL" 自动生成并注册,而 MyBatis 负责代理调用。
4. Spring 是如何管理这个代理 Bean 的?
- 当你使用
@MapperScan("com.example.mapper")时,MyBatis-Spring 提供了一个MapperScannerConfigurer。 - 它会把指定包下的所有接口,注册为 Spring Bean,Bean 的实例就是 MyBatis 生成的代理对象。
- 所以你可以用
@Autowired正常注入。
最终:
UserMapper在 Spring 容器中是一个 由 MyBatis 生成的代理对象,不是 null,也不是你写的类。
5、验证:打印 Mapper 实例的类名
java
System.out.println(userMapper.getClass().getName());
输出可能是:com.sun.proxy.$Proxy89
6、总结一句话:
MyBatis-Plus(基于 MyBatis)在运行时通过 JDK 动态代理,为 Mapper 接口自动生成了代理实现类,无需开发者手动编写。
补充说明:
- 这个"代理实现类"不是 MP 单独做的 ,而是 MyBatis 的核心机制,MP 是在此基础上增强了 CRUD 能力。
- 所以准确说是:MyBatis 负责生成代理,MP 负责填充通用 SQL 逻辑。
1-4、Service接口-小结

二、Controller中使用@Autowired注入Service接口而非实现类
在Spring MVC的Controller中使用@Autowired注入Service接口而非实现类,主要有以下几个原因:
1. 面向接口编程
这是面向对象设计的核心原则之一。依赖接口而非具体实现,可以降低代码耦合度,提高系统的可维护性和可扩展性。
2. 依赖倒置原则(DIP)
高层模块(Controller)不应该依赖低层模块(ServiceImpl),两者都应该依赖抽象(Service接口)。这样可以让代码更加灵活,易于修改。
3. 便于切换实现
如果业务需求变化,需要更换Service的实现方式,只需要:
- 创建新的实现类
- 修改配置或使用
@Primary、@Qualifier指定实现 - Controller代码无需修改
java
// Controller中的代码始终不变
@Autowired
private UserService userService;
// 可以轻松切换实现
@Service
public class UserServiceImpl implements UserService { }
@Service
@Primary // 优先使用这个实现
public class UserServiceImplV2 implements UserService { }
4. 便于单元测试
测试时可以轻松mock接口,而不需要依赖具体实现:
java
@SpringBootTest
class UserControllerTest {
@MockBean
private UserService userService; // 轻松mock接口
@Autowired
private UserController controller;
}
5. 支持AOP和代理
Spring的很多功能(如事务管理、缓存)是通过动态代理实现的。注入接口可以让Spring更灵活地创建代理对象。
6. 符合开闭原则
对扩展开放,对修改关闭。新增功能时只需增加新的实现类,而不修改现有代码。
注意: 即使注入的是接口,Spring在运行时注入的仍然是实现类的实例(或其代理对象),只是通过接口类型来引用它。