当前位置: 首页 > java >正文

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 {// 如需自定义方法,在此实现
}
  1. ServiceImpl<UserMapper, User> 指定泛型:UserMapper 是实体对应的 Mapper 接口,User 是实体类;
  2. @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);
}

http://www.xdnf.cn/news/16366.html

相关文章:

  • PYTHON从入门到实践-15数据可视化
  • 【资讯】2025年软件行业发展趋势:AI驱动变革,云原生与安全成核心
  • PHP框架之Laravel框架教程:1. laravel搭建
  • 亚马逊测评采购:如何打造安全的环境,技术基础关键
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 70(题目+回答)
  • Avantage6.6下载与安装教程
  • 差模干扰 共模干扰
  • 【隧道篇 / IPsec】(7.6) ❀ 01. 利用向导快速建立IPsec安全隧道 (点对点) ❀ FortiGate 防火墙
  • 详解力扣高频SQL50题之550. 游戏玩法分析 IV【中等】
  • ClickHouse高性能实时分析数据库-消费实时数据流(消费kafka)
  • MySQL进阶学习与初阶复习第三天
  • CSS3知识补充
  • 如何高效合并音视频文件(时间短消耗资源少)(二)
  • ICMPv4报文类型详解表
  • 人形机器人指南(八)操作
  • Xinference vs SGLang:详细对比分析
  • MybatisPlus-18.插件功能-分页插件基本用法
  • Jmeter的元件使用介绍:(五)定时器详解
  • 无需云服务器的内网穿透方案 -- cloudflare tunnel
  • 【AI周报】2025年7月26日
  • 什么是ICMP报文?有什么用?
  • Android Data Binding 深度解析与实践指南
  • easy-llm-cli的安装和使用
  • 【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程
  • Vue 3 与 Element Plus 中的 /deep/ 选择器问题
  • 论文阅读-RaftStereo
  • haproxy配置详解
  • QT核心————信号槽
  • 外带服务的温度:藏在包装里的“生活共情力”
  • [RPA] 日期时间练习案例