MyBatisPlus之CRUD接口—IService与BaseMapper
MyBatisPlus(MP)的核心优势之一是提供了开箱即用的CRUD接口,通过BaseMapper
(DAO层)和IService
(Service层)封装了单表操作的常用方法,无需编写SQL即可完成大部分数据库操作。
一、BaseMapper与IService的关系
在MyBatisPlus中,BaseMapper
和IService
是实现CRUD操作的两大核心接口,二者分工明确又相互配合:
- BaseMapper:位于DAO层,直接与数据库交互,提供基础的CRUD方法(如
insert
、selectById
),需由用户自定义的Mapper接口继承。 - IService:位于Service层,基于
BaseMapper
封装了更丰富的业务方法(如批量操作、分页查询),并提供事务支持,需由用户自定义的Service接口继承。
调用关系:IService
的实现类(如ServiceImpl
)会注入BaseMapper
实例,通过调用BaseMapper
的方法完成数据库操作,同时添加业务逻辑和事务控制。
使用建议:
- 简单查询直接使用
BaseMapper
; - 复杂业务(如批量操作、事务管理)优先使用
IService
; - 自定义SQL通过
BaseMapper
的方法扩展。
二、BaseMapper核心方法详解
BaseMapper<T>
是MP的基础接口,泛型T
为实体类类型。所有自定义Mapper接口只需继承它,即可获得17个基础CRUD方法。
2.1 新增操作(Insert)
方法签名 | 功能描述 | 示例 |
---|---|---|
int insert(T entity) |
插入一条记录 | userMapper.insert(user) |
说明:
- 插入时会根据实体类的注解(如
@TableId
)自动处理主键生成; - 若字段未设置值,会插入
null
(除非配置了自动填充); - 返回值为受影响的行数(成功插入返回1)。
示例:
User user = new User();
user.setUsername("张三");
user.setAge(20);
user.setEmail("zhangsan@example.com");
int rows = userMapper.insert(user); // 插入成功后,user.getId()会自动回填主键
System.out.println("插入行数:" + rows + ",生成ID:" + user.getId());
2.2 查询操作(Select)
BaseMapper
提供了7种查询方法,覆盖单条查询、批量查询、条件查询等场景:
方法签名 | 功能描述 | 适用场景 |
---|---|---|
T selectById(Serializable id) |
根据ID查询 | 已知主键的单条查询 |
List<T> selectBatchIds(Collection<?> ids) |
批量查询(根据ID集合) | 批量获取多条记录 |
List<T> selectByMap(Map<String, Object> map) |
根据Map条件查询 | 简单条件查询(键为字段名) |
T selectOne(@Param("ew") Wrapper<T> queryWrapper) |
根据条件查询单条 | 确保结果唯一的查询(如唯一索引) |
Integer selectCount(@Param("ew") Wrapper<T> queryWrapper) |
条件查询总数 | 统计符合条件的记录数 |
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper) |
条件查询列表 | 复杂条件的多条查询 |
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper) |
条件查询(返回Map) | 只需要部分字段,无需实体类 |
示例1:根据ID查询
User user = userMapper.selectById(1L); // ID为1的用户
示例2:批量查询
List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectBatchIds(ids); // 查询ID为1、2、3的用户
示例3:条件查询(使用QueryWrapper)
// 查询年龄≥20且用户名包含"张"的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 20) // 年龄≥20
.like("username", "张"); // 用户名含"张"
List<User> users = userMapper.selectList(queryWrapper);
示例4:查询总数
// 统计年龄<18的用户数量
Integer count = userMapper.selectCount(
new QueryWrapper<User>().lt("age", 18)
);
2.3 更新操作(Update)
方法签名 | 功能描述 | 适用场景 |
---|---|---|
int updateById(@Param("et") T entity) |
根据ID更新 | 已知主键,更新部分字段 |
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper) |
根据条件更新 | 按条件批量更新 |
示例1:根据ID更新
User user = new User();
user.setId(1L); // 必须设置ID
user.setAge(21); // 只更新年龄
int rows = userMapper.updateById(user); // SQL:UPDATE user SET age=21 WHERE id=1
示例2:条件更新
// 将所有年龄<18的用户状态改为"禁用"
User user = new User();
user.setStatus(StatusEnum.DISABLE);
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.lt("age", 18); // 条件:年龄<18
int rows = userMapper.update(user, updateWrapper);
// SQL:UPDATE user SET status=0 WHERE age < 18
2.4 删除操作(Delete)
方法签名 | 功能描述 | 适用场景 |
---|---|---|
int deleteById(Serializable id) |
根据ID删除 | 删除单条记录 |
int deleteBatchIds(Collection<?> ids) |
批量删除(根据ID集合) | 批量删除多条记录 |
int deleteByMap(Map<String, Object> map) |
根据Map条件删除 | 简单条件的批量删除 |
int delete(@Param("ew") Wrapper<T> queryWrapper) |
根据条件删除 | 复杂条件的批量删除 |
示例1:根据ID删除
int rows = userMapper.deleteById(1L); // 删除ID为1的用户
示例2:条件删除
// 删除邮箱为空的用户
int rows = userMapper.delete(
new QueryWrapper<User>().isNull("email")
);
三、IService核心方法详解
IService<T>
是Service层的接口,基于BaseMapper
扩展了更丰富的方法,尤其适合复杂业务场景。自定义Service接口需继承IService
,实现类需继承ServiceImpl<Mapper, T>
。
3.1 新增操作(Save)
方法签名 | 功能描述 | 与BaseMapper的区别 |
---|---|---|
boolean save(T entity) |
插入一条记录 | 返回boolean(成功/失败),BaseMapper返回int |
boolean saveBatch(Collection<T> entityList) |
批量插入 | 内部默认分批插入(默认1000条/批) |
boolean saveBatch(Collection<T> entityList, int batchSize) |
自定义批次大小的批量插入 | 可指定每批插入数量 |
示例1:单条插入
User user = new User();
user.setUsername("李四");
boolean success = userService.save(user); // 成功返回true
示例2:批量插入
List<User> userList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
User user = new User();
user.setUsername("用户" + i);
user.setAge(18 + i % 20);
userList.add(user);
}
// 批量插入(默认每批1000条)
boolean success = userService.saveBatch(userList);
// 自定义每批50条
// userService.saveBatch(userList, 50);
3.2 查询操作(Get/List)
IService
的查询方法在BaseMapper
基础上增加了分页查询和链式调用支持:
方法签名 | 功能描述 | 特色功能 |
---|---|---|
T getById(Serializable id) |
根据ID查询 | 同selectById ,返回null 时无异常 |
List<T> listByIds(Collection<?> ids) |
批量查询(ID集合) | 同selectBatchIds |
List<T> list(Wrapper<T> queryWrapper) |
条件查询列表 | 同selectList |
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper) |
分页查询 | 支持分页插件,返回分页对象 |
long count(Wrapper<T> queryWrapper) |
条件查询总数 | 同selectCount ,返回long类型 |
boolean exists(Wrapper<T> queryWrapper) |
判断是否存在符合条件的记录 | 简化count > 0 的判断 |
示例1:分页查询
// 分页查询:第2页,每页10条,条件:年龄≥20
Page<User> page = new Page<>(2, 10); // 页码从1开始
IPage<User> userPage = userService.page(
page,
new QueryWrapper<User>().ge("age", 20)
);
List<User> records = userPage.getRecords(); // 当前页数据
long total = userPage.getTotal(); // 总条数
long pages = userPage.getPages(); // 总页数
示例2:判断记录是否存在
// 判断是否存在用户名=张三的用户
boolean exists = userService.exists(
new QueryWrapper<User>().eq("username", "张三")
);
3.3 更新操作(Update)
方法签名 | 功能描述 | 特色功能 |
---|---|---|
boolean updateById(T entity) |
根据ID更新 | 同updateById ,返回boolean |
boolean update(Wrapper<T> updateWrapper) |
根据条件更新(无实体类) | 直接通过Wrapper设置更新字段 |
boolean update(T entity, Wrapper<T> updateWrapper) |
根据条件更新 | 同BaseMapper.update |
boolean updateBatchById(Collection<T> entityList) |
批量更新(根据ID) | 批量更新多条记录 |
boolean updateBatchById(Collection<T> entityList, int batchSize) |
自定义批次的批量更新 | 可指定每批更新数量 |
示例1:通过Wrapper直接更新
// 无需实体类,直接设置更新字段
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("username", "张三") // 条件
.set("age", 22) // 更新字段
.set("email", "new@example.com");
boolean success = userService.update(updateWrapper);
示例2:批量更新
List<User> userList = new ArrayList<>();
// 假设userList中包含多个设置了ID和待更新字段的User对象
boolean success = userService.updateBatchById(userList);
3.4 删除操作(Remove)
方法签名 | 功能描述 | 与BaseMapper的区别 |
---|---|---|
boolean removeById(Serializable id) |
根据ID删除 | 返回boolean,BaseMapper返回int |
boolean removeByIds(Collection<?> ids) |
批量删除(ID集合) | 同deleteBatchIds ,返回boolean |
boolean remove(Wrapper<T> queryWrapper) |
根据条件删除 | 同delete ,返回boolean |
boolean removeByMap(Map<String, Object> map) |
根据Map条件删除 | 同deleteByMap ,返回boolean |
示例:
// 根据条件删除
boolean success = userService.remove(
new QueryWrapper<User>().eq("status", StatusEnum.DISABLE)
);
3.5 其他实用方法
方法签名 | 功能描述 | 示例 |
---|---|---|
T getOne(Wrapper<T> queryWrapper, boolean throwEx) |
查询单条,支持是否抛异常 | getOne(wrapper, true) 结果不唯一时抛异常 |
List<T> list(IPage<T> page, Wrapper<T> queryWrapper) |
分页查询(返回List) | 只获取分页数据,忽略总条数 |
boolean saveOrUpdate(T entity) |
新增或更新(根据ID判断) | 有ID则更新,无ID则插入 |
boolean saveOrUpdateBatch(Collection<T> entityList) |
批量新增或更新 | 批量处理新增/更新 |
示例:saveOrUpdate(新增或更新)
User user1 = new User();
user1.setId(1L); // 有ID → 更新
user1.setAge(23);
User user2 = new User();
user2.setUsername("新用户"); // 无ID → 新增
userService.saveOrUpdate(user1);
userService.saveOrUpdate(user2);
四、BaseMapper与IService的选择策略
场景 | 推荐使用 | 理由 |
---|---|---|
简单CRUD操作(单表) | 优先IService | 方法返回boolean,更符合业务逻辑判断;支持批量操作 |
复杂查询(多条件) | IService + Wrapper | 链式调用更简洁,支持分页 |
事务管理 | IService | Service层天然适合控制事务(@Transactional ) |
自定义SQL | BaseMapper | 需在Mapper接口中定义方法,通过@Select 等注解实现 |
批量操作(1000+条) | IService的批量方法 | 内置分批处理,避免SQL过长导致性能问题 |
分布式事务 | IService | 结合@Transactional(rollbackFor = Exception.class) 确保事务一致性 |
五、实战技巧与避坑指南
5.1 Wrapper的灵活使用
LambdaQueryWrapper:避免硬编码字段名,推荐使用:
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>(); lambdaWrapper.ge(User::getAge, 20) // 引用方法,类型安全 .like(User::getUsername, "张");
条件判断:通过
if
动态组装条件:LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); if (minAge != null) { wrapper.ge(User::getAge, minAge); } if (keyword != null) { wrapper.like(User::getUsername, keyword) .or() .like(User::getEmail, keyword); }
5.2 批量操作的性能优化
- 设置合理的批次大小:
saveBatch
默认每批1000条,可根据数据库性能调整(如MySQL建议500-1000条/批); - 关闭批量操作的自动填充:若无需更新时间等字段,可通过全局配置关闭,提升性能;
- 使用
INSERT INTO ... VALUES (...), (...)
语法:MP的批量插入默认使用此语法,比循环单条插入效率高10倍以上。
5.3 避免N+1查询问题
当查询列表后需要根据关联ID查询其他表时,容易出现N+1问题(1次查列表,N次查详情)。解决方案:
- 手动编写联表查询SQL(通过
BaseMapper
扩展); - 使用MP的
@TableField
关联查询(适合简单关联); - 结合
PageHelper
等插件实现分页联表查询。
5.4 逻辑删除与查询的注意事项
- 逻辑删除后,
BaseMapper
和IService
的查询方法会自动过滤已删除数据(添加is_deleted = 0
条件); - 自定义SQL需手动添加逻辑删除。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ