MyBatis-Plus 通用 Service 详解:IService 与 CRUD 操作全解析
在日常开发中,重复的 CRUD 操作往往占据大量开发时间。MyBatis-Plus 提供的 IService
接口及其实现类 ServiceImpl
封装了常用的业务层逻辑,极大简化了开发流程。本文将详细解析 IService
和 ServiceImpl
的源码设计,并通过实例演示如何使用它们实现高效的 CRUD 操作。
一、前言:为什么需要 IService?
MyBatis-Plus 的通用 Service 层(IService
)旨在进一步封装 CRUD 操作,通过统一的方法命名(如 get
查单行、remove
删除、list
查集合、page
分页)区分 Mapper 层方法,避免混淆。其核心优势在于:
- 封装常用业务逻辑,减少重复代码;
- 支持批量操作(如批量新增、批量更新),提升性能;
- 提供灵活的条件查询(结合 Wrapper),简化复杂查询;
- 泛型设计适配任意实体类,通用性强。
官方文档:https://baomidou.com/pages/49cc81/#service-crud - 接口
二、IService 接口:通用 CRUD 方法定义
IService
是 MyBatis-Plus 提供的通用 Service 接口,泛型 T
代表实体类类型,定义了大量默认方法(default
修饰),涵盖了 CRUD 及批量操作的核心逻辑。
2.1 IService 核心方法解析
(1)新增操作
// 保存单个实体
default boolean save(T entity) {return SqlHelper.retBool(this.getBaseMapper().insert(entity));
}// 批量保存(默认批量大小 1000)
@Transactional(rollbackFor = {Exception.class})
default boolean saveBatch(Collection<T> entityList) {return this.saveBatch(entityList, 1000);
}// 批量保存(指定批量大小)
boolean saveBatch(Collection<T> entityList, int batchSize);// 批量保存或更新(默认批量大小 1000)
@Transactional(rollbackFor = {Exception.class})
default boolean saveOrUpdateBatch(Collection<T> entityList) {return this.saveOrUpdateBatch(entityList, 1000);
}// 批量保存或更新(指定批量大小)
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
(2)删除操作
// 根据 ID 删除
default boolean removeById(Serializable id) {return SqlHelper.retBool(this.getBaseMapper().deleteById(id));
}// 根据 ID 列表批量删除
default boolean removeByIds(Collection<?> list) {return CollectionUtils.isEmpty(list) ? false : SqlHelper.retBool(this.getBaseMapper().deleteBatchIds(list));
}// 根据条件删除(Wrapper)
default boolean remove(Wrapper<T> queryWrapper) {return SqlHelper.retBool(this.getBaseMapper().delete(queryWrapper));
}// 根据 Map 条件删除(key=列名,value=值)
default boolean removeByMap(Map<String, Object> columnMap) {Assert.notEmpty(columnMap, "columnMap must not be empty");return SqlHelper.retBool(this.getBaseMapper().deleteByMap(columnMap));
}
(3)更新操作
// 根据 ID 更新
default boolean updateById(T entity) {return SqlHelper.retBool(this.getBaseMapper().updateById(entity));
}// 根据 Wrapper 更新
default boolean update(Wrapper<T> updateWrapper) {return this.update(null, updateWrapper);
}// 根据实体 + Wrapper 更新
default boolean update(T entity, Wrapper<T> updateWrapper) {return SqlHelper.retBool(this.getBaseMapper().update(entity, updateWrapper));
}// 批量更新(默认批量大小 1000)
@Transactional(rollbackFor = {Exception.class})
default boolean updateBatchById(Collection<T> entityList) {return this.updateBatchById(entityList, 1000);
}// 批量更新(指定批量大小)
boolean updateBatchById(Collection<T> entityList, int batchSize);
(4)查询操作
// 根据 ID 查询
default T getById(Serializable id) {return this.getBaseMapper().selectById(id);
}// 根据 ID 列表查询
default List<T> listByIds(Collection<? extends Serializable> idList) {return this.getBaseMapper().selectBatchIds(idList);
}// 根据条件查询单条(Wrapper)
default T getOne(Wrapper<T> queryWrapper) {return this.getOne(queryWrapper, true);
}// 分页查询
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {return this.getBaseMapper().selectPage(page, queryWrapper);
}// 查询总数
default long count(Wrapper<T> queryWrapper) {return SqlHelper.retCount(this.getBaseMapper().selectCount(queryWrapper));
}
2.2 默认方法的优势
IService
中大量使用 default
方法,使得实现类无需强制重写即可直接使用基础功能,同时允许子类根据需求重写自定义逻辑,兼顾了通用性和灵活性。
三、ServiceImpl:IService 的默认实现
ServiceImpl<M extends BaseMapper<T>, T>
是 IService
的默认实现类,通过依赖注入 BaseMapper
实现数据库交互,同时处理了批量操作、事务等细节。
3.1 ServiceImpl 核心逻辑
(1)成员变量与依赖注入
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {protected Log log = LogFactory.getLog(this.getClass());@Autowiredprotected M baseMapper; // 注入 BaseMapper 实例protected Class<T> entityClass = this.currentModelClass(); // 实体类 Classprotected Class<M> mapperClass = this.currentMapperClass(); // Mapper 接口 Class// ...
}
baseMapper
由 Spring 自动注入,是与数据库交互的核心组件;entityClass
和 mapperClass
通过反射获取泛型类型,用于处理实体元数据。
(2)批量操作实现
以 saveBatch
为例,ServiceImpl
实现了批量插入逻辑:
@Transactional(rollbackFor = {Exception.class})
public boolean saveBatch(Collection<T> entityList, int batchSize) {String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE); // 获取插入 SQLreturn this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {sqlSession.insert(sqlStatement, entity); // 批量执行插入});
}// 批量执行模板方法
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
}
批量操作通过 SqlSession
批量提交,减少数据库交互次数,提升性能。
(3)saveOrUpdate 逻辑
saveOrUpdate
自动判断是新增还是更新:
public boolean saveOrUpdate(T entity) {if (null == entity) {return false;}TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);String keyProperty = tableInfo.getKeyProperty(); // 获取主键字段Object idVal = tableInfo.getPropertyValue(entity, keyProperty); // 获取实体主键值// 主键不为空且存在则更新,否则新增return !StringUtils.checkValNull(idVal) && !Objects.isNull(this.getById((Serializable) idVal)) ? this.updateById(entity) : this.save(entity);
}
四、实战:创建 Service 接口与实现类
使用 IService
和 ServiceImpl
只需简单两步:定义接口继承 IService
,实现类继承 ServiceImpl
并实现自定义接口。
4.1 定义 Service 接口
/*** UserService 继承 IService,获得基础 CRUD 能力*/
public interface UserService extends IService<User> {// 如需自定义方法,在此声明
}
4.2 实现 Service 接口
/*** 实现类继承 ServiceImpl,注入 UserMapper 并实现 UserService*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {// 如需自定义方法,在此实现
}
ServiceImpl<UserMapper, User>
指定泛型:UserMapper
是实体对应的 Mapper 接口,User
是实体类;@Service
注解将实现类注册为 Spring 组件,可通过@Autowired
注入使用。
4.3 依赖注入使用
@Autowired
private UserService userService; // 注入 UserService(实际是 UserServiceImpl 实例)
五、CRUD 操作测试示例
以下通过测试用例演示 IService
的核心操作。
一、新增操作
1. 保存单个实体(save
)
源码(IService):
/*** 保存单个实体对象到数据库* @param entity 要保存的实体对象* @return 保存操作是否成功的布尔值*/
default boolean save(T entity) {// 调用基础映射器的 insert 方法插入实体对象,并通过 SqlHelper 将结果转换为布尔值return SqlHelper.retBool(this.getBaseMapper().insert(entity));
}
测试方法:
/*** 测试保存单个实体*/
@Test
public void testSave() {User user = new User();user.setId(9L); // 若主键非自增,需指定唯一IDuser.setName("张三");user.setAge(22);user.setEmail("zhangsan@example.com");// 执行保存操作boolean success = userService.save(user);System.out.println("保存单个实体成功?" + success); // 输出:true
}
2. 批量保存(默认批量大小,saveBatch
)
源码(IService):
/*** 批量保存实体对象到数据库,使用默认批量大小* @param entityList 要保存的实体对象集合* @return 批量保存操作是否成功的布尔值*/
@Transactional(rollbackFor = {Exception.class})
default boolean saveBatch(Collection<T> entityList) {// 调用带批量大小参数的 saveBatch 方法,使用默认批量大小 1000return this.saveBatch(entityList, 1000);
}
测试方法:
/*** 测试批量保存(默认批量大小1000)*/
@Test
public void testSaveBatch() {List<User> userList = new ArrayList<>();for (int i = 0; i < 5; i++) {User user = new User();user.setName("批量用户" + i);user.setAge(20 + i);user.setEmail("batch" + i + "@example.com");userList.add(user);}// 执行批量保存boolean success = userService.saveBatch(userList);System.out.println("批量保存成功?" + success); // 输出:true
}
3. 批量保存(指定批量大小,saveBatch
)
源码(IService):
/*** 批量保存实体对象到数据库,指定批量大小* @param entityList 要保存的实体对象集合* @param batchSize 批量大小* @return 批量保存操作是否成功的布尔值*/
boolean saveBatch(Collection<T> entityList, int batchSize);
实现(ServiceImpl):
@Transactional(rollbackFor = {Exception.class})
public boolean saveBatch(Collection<T> entityList, int batchSize) {String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {sqlSession.insert(sqlStatement, entity);});
}
测试方法:
/*** 测试批量保存(指定批量大小)*/
@Test
public void testSaveBatchWithSize() {List<User> userList = new ArrayList<>();for (int i = 5; i < 10; i++) {User user = new User();user.setName("指定批量用户" + i);user.setAge(25 + i);userList.add(user);}// 执行批量保存,指定批量大小为5boolean success = userService.saveBatch(userList, 5);System.out.println("指定批量大小保存成功?" + success); // 输出:true
}
4. 批量保存或更新(默认批量大小,saveOrUpdateBatch
)
源码(IService):
/*** 批量保存或更新实体对象到数据库,使用默认批量大小* @param entityList 要保存或更新的实体对象集合* @return 批量保存或更新操作是否成功的布尔值*/
@Transactional(rollbackFor = {Exception.class})
default boolean saveOrUpdateBatch(Collection<T> entityList) {return this.saveOrUpdateBatch(entityList, 1000);
}
测试方法:
/*** 测试批量保存或更新(默认批量大小)*/
@Test
public void testSaveOrUpdateBatch() {List<User> userList = new ArrayList<>();// 已存在的实体(更新)User updateUser = new User();updateUser.setId(1L);updateUser.setName("更新后的管理员");userList.add(updateUser);// 新实体(新增)User newUser = new User();newUser.setName("全新用户");newUser.setAge(30);userList.add(newUser);// 执行批量保存或更新boolean success = userService.saveOrUpdateBatch(userList);System.out.println("批量保存或更新成功?" + success); // 输出:true
}
二、删除操作
1. 根据 ID 删除(removeById
)
源码(IService):
/*** 根据实体对象的 ID 删除该对象* @param id 要删除的实体对象的 ID* @return 删除操作是否成功的布尔值*/
default boolean removeById(Serializable id) {return SqlHelper.retBool(this.getBaseMapper().deleteById(id));
}
测试方法:
/*** 测试根据ID删除*/
@Test
public void testRemoveById() {Long id = 9L; // 要删除的IDboolean success = userService.removeById(id);System.out.println("根据ID删除成功?" + success); // 输出:true(若ID存在)
}
2. 根据 ID 列表批量删除(removeByIds
)
源码(IService):
/*** 根据 ID 列表批量删除实体对象* @param list 要删除的实体对象的 ID 列表* @return 删除操作是否成功的布尔值*/
default boolean removeByIds(Collection<?> list) {return CollectionUtils.isEmpty(list) ? false : SqlHelper.retBool(this.getBaseMapper().deleteBatchIds(list));
}
测试方法:
/*** 测试根据ID列表批量删除*/
@Test
public void testRemoveByIds() {List<Long> ids = Arrays.asList(2L, 3L, 4L); // 要删除的ID列表boolean success = userService.removeByIds(ids);System.out.println("批量删除成功?" + success); // 输出:true(若ID存在)
}
3. 根据条件删除(remove
)
源码(IService):
/*** 根据查询条件包装器删除实体对象* @param queryWrapper 查询条件包装器* @return 删除操作是否成功的布尔值*/
default boolean remove(Wrapper<T> queryWrapper) {return SqlHelper.retBool(this.getBaseMapper().delete(queryWrapper));
}
测试方法:
/*** 测试根据条件删除(Wrapper)*/
@Test
public void testRemoveByWrapper() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.isNull("email").or().lt("age", 18); // 删除邮箱为空或年龄<18的记录boolean success = userService.remove(queryWrapper);System.out.println("条件删除成功?" + success); // 输出:true(若有匹配记录)
}
三、更新操作
1. 根据 ID 更新(updateById
)
源码(IService):
/*** 根据实体对象的 ID 更新该对象* @param entity 要更新的实体对象* @return 更新操作是否成功的布尔值*/
default boolean updateById(T entity) {return SqlHelper.retBool(this.getBaseMapper().updateById(entity));
}
测试方法:
/*** 测试根据ID更新*/
@Test
public void testUpdateById() {User user = new User();user.setId(1L); // 要更新的IDuser.setAge(35); // 仅更新年龄字段boolean success = userService.updateById(user);System.out.println("根据ID更新成功?" + success); // 输出:true(若ID存在)
}
2. 根据条件更新(update
)
源码(IService):
/*** 根据更新条件包装器更新实体对象* @param updateWrapper 更新条件包装器* @return 更新操作是否成功的布尔值*/
default boolean update(Wrapper<T> updateWrapper) {return this.update(null, updateWrapper);
}
测试方法:
/*** 测试根据条件更新(Wrapper)*/
@Test
public void testUpdateByWrapper() {UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();updateWrapper.set("email", "default@example.com").gt("age", 40); // 年龄>40的用户统一更新邮箱boolean success = userService.update(updateWrapper);System.out.println("条件更新成功?" + success); // 输出:true(若有匹配记录)
}
3. 批量更新(updateBatchById
)
源码(IService):
/*** 根据 ID 批量更新实体对象,使用默认批量大小* @param entityList 要更新的实体对象集合* @return 批量更新操作是否成功的布尔值*/
@Transactional(rollbackFor = {Exception.class})
default boolean updateBatchById(Collection<T> entityList) {return this.updateBatchById(entityList, 1000);
}
实现(ServiceImpl):
@Transactional(rollbackFor = {Exception.class})
public boolean updateBatchById(Collection<T> entityList, int batchSize) {String sqlStatement = this.getSqlStatement(SqlMethod.UPDATE_BY_ID);return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {ParamMap<T> param = new ParamMap<>();param.put("et", entity);sqlSession.update(sqlStatement, param);});
}
测试方法:
/*** 测试批量更新*/
@Test
public void testUpdateBatchById() {List<User> userList = new ArrayList<>();// 第一个更新对象User user1 = new User();user1.setId(1L);user1.setName("批量更新1号");userList.add(user1);// 第二个更新对象User user2 = new User();user2.setId(5L);user2.setAge(40);userList.add(user2);// 执行批量更新boolean success = userService.updateBatchById(userList);System.out.println("批量更新成功?" + success); // 输出:true(若ID存在)
}
四、查询操作
1. 根据 ID 查询(getById
)
源码(IService):
/*** 根据实体对象的 ID 获取该对象* @param id 要获取的实体对象的 ID* @return 获取到的实体对象*/
default T getById(Serializable id) {return this.getBaseMapper().selectById(id);
}
测试方法:
/*** 测试根据ID查询*/
@Test
public void testGetById() {Long id = 1L; // 要查询的IDUser user = userService.getById(id);System.out.println("查询结果:" + user); // 输出:User(id=1, name=..., ...)
}
2. 分页查询(page
)
源码(IService):
/*** 根据分页对象和查询条件包装器进行分页查询* @param page 分页对象* @param queryWrapper 查询条件包装器* @param <E> 分页对象的类型* @return 分页查询结果*/
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {return this.getBaseMapper().selectPage(page, queryWrapper);
}
测试方法:
/*** 测试分页查询(需提前配置分页插件)*/
@Test
public void testPage() {// 分页插件配置类(需单独定义)// @Configuration// public class MyBatisPlusConfig {// @Bean// public MybatisPlusInterceptor interceptor() {// MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));// return interceptor;// }// }Page<User> page = new Page<>(1, 3); // 第1页,每页3条QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.ge("age", 20); // 条件:年龄>=20IPage<User> result = userService.page(page, queryWrapper);System.out.println("总记录数:" + result.getTotal());System.out.println("总页数:" + result.getPages());System.out.println("当前页数据:" + result.getRecords());
}
3. 条件查询单条(getOne
)
源码(IService):
/*** 根据查询条件包装器获取单个实体对象,默认抛出异常时返回对象* @param queryWrapper 查询条件包装器* @return 获取到的单个实体对象*/
default T getOne(Wrapper<T> queryWrapper) {return this.getOne(queryWrapper, true);
}
实现(ServiceImpl):
public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {return throwEx ? this.baseMapper.selectOne(queryWrapper) : SqlHelper.getObject(this.log, this.baseMapper.selectList(queryWrapper));
}
测试方法:
/*** 测试条件查询单条*/
@Test
public void testGetOne() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", "张三"); // 条件:姓名=张三User user = userService.getOne(queryWrapper); // 若多条会抛异常System.out.println("查询结果:" + user);
}