从 selectBatchIds 切入:MyBatis-Plus 批量查询工具与 Java 集合、泛型的联动学习文档

一、引子

​ 在后端开发中,「根据多个 ID 批量查询数据」是极为高频的业务场景 —— 例如前端传递一个 ID 列表,后端需一次性获取这些 ID 对应的完整记录。若采用循环调用单条查询接口的方案,会频繁建立数据库连接,大幅增加 IO 开销,严重影响系统性能。

​ MyBatis-Plus 提供的 selectBatchIds 方法恰好解决了这一痛点:它通过一次 SQL 执行完成批量查询,有效减少数据库交互次数,显著提升效率。

在项目实践中频繁使用该方法时,我曾查阅其源码实现,发现其中集合与泛型的应用设计十分典型,借此机会梳理相关知识点,形成以下学习记录。该方法的核心定义如下:

List<T>  selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);  

​ 从方法签名可直观发现两处关键技术点,也是本文的核心学习内容:

• 参数类型 Collection<? extends Serializable>:深度融合 Java 集合框架的设计思想与类型约束逻辑;

• 返回值 List:基于泛型实现类型安全保障与代码复用,是 MyBatis-Plus 通用 CRUD 能力的核心支撑。

二、参数类型 Collection<? extends Serializable> 涉及「集合框架」的使用与约束;

​ selectBatchIds 的参数声明看似简洁,实则蕴含 Java 集合框架与泛型的底层设计逻辑。我们从「集合接口选型」与「泛型通配符约束」两个维度拆解分析。

1.为什么选择Collection 作为接口类型?

​ Collection 是 Java 集合框架的顶层接口之一(位于 java.util 包),是 List、Set、Queue 等所有「单值集合」的父接口,定义了集合的通用操作规范(如 add() 新增元素、size() 获取长度、isEmpty() 判断空值等)。

selectBatchIds 选择 Collection 而非具体实现类(如 ArrayList、HashSet),本质是「面向接口编程」思想的典型实践,核心优势体现在两方面:

(1)更高的灵活性:适配多场景集合输入

• 若需保留 ID 传入顺序(如按前端传递顺序返回结果),可传入 List:

//  传入 List,保留 ID 顺序   
List<Long> idList = Arrays.asList(1L, 2L, 3L);   userMapper.selectBatchIds(idList);  

• 若需先对 ID 去重(如避免重复查询同一记录),可传入 Set:

//  传入 Set,自动去重   
Set<Integer> idSet = new HashSet<>(Arrays.asList(1, 2, 3, 3));   
userMapper.selectBatchIds(idSet);  

(2)更强的解耦性:依赖抽象而非具体实现

​ Collection 接口仅定义「行为规范」(如遍历、判断空值),不绑定具体实现逻辑。这使得 selectBatchIds 的底层处理(如遍历 ID 生成 IN 条件的 SQL 占位符)可基于抽象接口编写,无需关注传入集合的具体类型(是 List 还是 Set),避免与特定实现耦合,降低后续维护成本。

2.泛型通配符 <? extends Serializable> 是什么意思?

该通配符是 Java 泛型中的「上限通配符」(Upper Bounded Wildcard),核心含义是:集合中的元素必须是 Serializable 接口的实现类(Serializable 本身是接口,实际约束的是其所有实现类)。

(1) 通配符拆解

• ?:表示「未知类型」,是泛型通配符的基础形式,用于表示不确定的类型占位;

• extends Serializable:为「未知类型」设定上限 —— 限定该类型必须是 Serializable 的子类(或实现类,Java 中接口间继承用 extends 关键字)。

(2) Serializable接口的核心作用

Serializable 是 Java 中的「标记接口」(无任何方法或字段定义),仅作为一种「能力标识」:告诉 JVM “该类的对象支持序列化”。

序列化的核心目的是实现对象的跨场景复用:

• 跨网络传输:如微服务间通过 RPC 传递对象(需转为字节流);

• 缓存存储:如将对象存入 Redis(需序列化为字符串或字节数组);

• 持久化:如将对象写入本地文件(需转为字节流)。

而数据库表的主键(ID)常用类型(Integer、Long、String 等)均已实现 Serializable 接口,恰好满足该约束。

(3)约束的价值:提前规避类型错误

​ 通过 <? extends Serializable> 约束,可在编译期拦截不合法的 ID 类型(如未实现 Serializable 的自定义类),避免运行时抛出 NotSerializableException,减少线上故障风险。

三、返回值 List 则用到了「泛型」来保证类型安全与代码复用。

​ selectBatchIds 的返回值 List 是泛型的经典应用。其中 并非固定类型,而是与 MyBatis-Plus 的 BaseMapper 接口泛型参数绑定 —— 当自定义 Mapper 接口继承 BaseMapper 并指定实体类时,T 会自动映射为该实体类型,selectBatchIds 的返回值也随之变为 List<实体类>。这种设计同时实现了「类型安全」与「代码复用」,是 MyBatis-Plus 通用 CRUD 能力的核心。

1.泛型参数T的绑定逻辑

要理解 List,需先明确 T 的来源:BaseMapper 本身是泛型接口,T 代表「业务实体类类型」,其定义简化如下:

//  BaseMapper 是泛型接口,T 代表“实体类类型”   
public interface BaseMapper<T> {     
    // 返回值  List<T>:T 与接口的泛型参数一致,即“当前实体类的列表”
    List<T>  selectBatchIds(@Param("coll") Collection<? extends  Serializable> idList);          
    // 其他方法也依赖  T,如单条查询返回 T     
    T selectById(Serializable id);   
}  

当我们自定义 Mapper 接口时,会通过泛型参数传递,将 T 绑定到具体的实体类:

//  UserMapper 继承 BaseMapper 时,将 T 指定为 User   
public interface UserMapper extends BaseMapper<User> {     // 无需重写  selectBatchIds,继承的方法自动适配为:     
    List<User>  selectBatchIds(Collection<? extends Serializable> idList);  
}  

这一绑定过程的核心价值在于:selectBatchIds 无需针对每个实体类单独定义,仅通过泛型参数即可自动适配不同业务实体,实现 “一次定义,多场景复用”。

2.泛型如何实现代码复用?

​ selectBatchIds 能成为所有 Mapper 的通用方法,核心依赖泛型 T 的「类型参数化」能力 —— 将 “实体类类型” 从方法逻辑中剥离,用 T 作为占位符,使方法适配任意实体。

(1) 无泛型的弊端:代码冗余严重

假设没有泛型,我们需要为每个实体类重复编写几乎相同的方法:

//  无泛型:为 User 写一次   
public interface UserMapper {     
    List<User>  selectUserBatchIds(Collection<? extends Serializable> idList);   
}      
// 无泛型:为 Order 再写一次(逻辑完全相同,只是类型不同)   
public interface OrderMapper {     
    List<Order>  selectOrderBatchIds(Collection<? extends Serializable> idList);   
}      
// 无泛型:为 Product 还要写...(代码冗余爆炸)   
public interface ProductMapper {     
    List<Product>  selectProductBatchIds(Collection<? extends Serializable> idList);   
}  

(2) 泛型的优势:一次定义,全局复用

​ 通过 BaseMapper 的泛型设计,仅需定义一次 selectBatchIds,所有继承 BaseMapper 的自定义 Mapper 均可直接使用,且自动适配对应实体类型:

//  泛型方案:一份 BaseMapper 适配所有实体   
public interface UserMapper extends BaseMapper<User> {} // 自动有 List<User> 方法   
public interface OrderMapper extends BaseMapper<Order> {} // 自动有 List<Order> 方法   
public interface ProductMapper extends BaseMapper<Product> {} // 自动有 List<Product> 方法  

​ 这种设计不仅消除了代码冗余,还保证了各 Mapper 方法逻辑的一致性,大幅降低维护成本。

3. 泛型如何保障类型安全?

​ 泛型的另一核心价值是「类型安全」—— 通过编译期类型校验,避免运行时类型转换异常,同时减少手动类型转换的繁琐操作。

(1) 编译期拦截非法类型

​ 若未使用泛型,返回值需定义为 List

posted on 2025-09-17 09:24  下雨不收衣  阅读(37)  评论(0)    收藏  举报

导航