一、核心概念
1. ServiceImpl
是什么?
ServiceImpl<M, T>
是 MyBatis-Plus 提供的 通用 Service 层实现基类,封装了常见的 CRUD 操作,开发者无需重复编写基础业务逻辑,可快速构建 Service 层。
✅ 本质:对
BaseMapper
的进一步封装,提供更贴近业务的 API。
2. 继承关系图解
+---------------------+
| IService<T> | ← 定义通用方法(如 save, list, page)
+----------+----------+
|
| 实现
v
+---------------------+
| ServiceImpl<M, T> | ← 提供默认实现(依赖 BaseMapper)
+----------+----------+
|
| 继承(你的业务 Service)
v
+-------------------------------+
| UserServiceImpl extends |
| ServiceImpl<UserMapper, User> |
+-------------------------------+
3. 核心优势
优势 | 说明 |
---|---|
减少模板代码 | 无需手动调用 mapper.insert() 等 |
链式调用支持 | 如 lambdaQuery().eq(...).list() |
事务默认集成 | 多操作方法(如 saveBatch )自带事务 |
与 Wrapper 深度集成 | 支持 QueryWrapper , LambdaQueryWrapper |
易于扩展 | 可在子类中重写或新增方法 |
二、操作步骤(超详细,适合快速上手)
步骤 1:创建实体类(Entity)
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
private LocalDateTime createTime;
private Integer status; // 0:禁用, 1:启用
}
步骤 2:创建 Mapper 接口(继承 BaseMapper
)
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 可在此添加自定义 SQL 方法
}
✅
BaseMapper<User>
提供了insert
,selectById
,updateById
,deleteById
等基础方法。
步骤 3:创建 Service 接口(继承 IService
)
public interface UserService extends IService<User> {
// 可在此定义业务方法
List<User> getUsersByAgeRange(Integer minAge, Integer maxAge);
void disableUser(Long id);
}
✅
IService<User>
提供了save
,removeById
,list
,page
等通用方法。
步骤 4:创建 Service 实现类(继承 ServiceImpl
)
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 自定义业务方法:根据年龄范围查询用户
*/
@Override
public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {
return this.lambdaQuery()
.ge(User::getAge, minAge)
.le(User::getAge, maxAge)
.list();
}
/**
* 自定义业务方法:禁用用户(带业务逻辑)
*/
@Override
@Transactional
public void disableUser(Long id) {
User user = this.getById(id);
if (user == null) {
throw new IllegalArgumentException("用户不存在:" + id);
}
if (user.getStatus().equals(0)) {
log.warn("用户 {} 已被禁用", id);
return;
}
user.setStatus(0);
boolean success = this.updateById(user);
if (success) {
log.info("用户 {} 已成功禁用", id);
// 可在此触发事件,如发送通知
} else {
throw new RuntimeException("禁用用户失败:" + id);
}
}
/**
* 批量保存用户(演示 ServiceImpl 的内置方法)
*/
public boolean saveUsersBatch(List<User> users) {
return this.saveBatch(users, 100); // 每批 100 条
}
}
🔑 关键点:
extends ServiceImpl<UserMapper, User>
:泛型 1 是 Mapper 类型,泛型 2 是实体类型implements UserService
:实现业务接口this.lambdaQuery()
:调用ServiceImpl
提供的便捷查询链
步骤 5:在 Controller 中使用 Service
@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
@PostMapping
public String createUser(@RequestBody User user) {
boolean saved = userService.save(user);
return saved ? "success" : "fail";
}
@GetMapping("/age")
public List<User> getUsersByAge(@RequestParam Integer min, @RequestParam Integer max) {
return userService.getUsersByAgeRange(min, max);
}
@DeleteMapping("/{id}")
public String disable(@PathVariable Long id) {
userService.disableUser(id);
return "disabled";
}
}
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
NoSuchBeanDefinitionException: No qualifying bean of type 'UserService' |
@Service 注解缺失或未扫描 |
检查 @Service 和 @ComponentScan |
Mapper 为 null |
ServiceImpl 未正确注入 BaseMapper |
确保继承 ServiceImpl<M, T> 且 M 是 Mapper 接口 |
saveBatch 不回滚 |
异常被捕获未抛出 | 确保在 @Transactional 方法中抛出运行时异常 |
lambdaQuery() 报错 |
MyBatis-Plus 版本过低 | 升级到 3.4.0+ |
getById 返回 null 但数据库有数据 |
逻辑删除字段问题 | 检查 @TableLogic 配置和 deleted 字段值 |
四、注意事项
必须添加
@Service
:
否则 Spring 无法管理该 Bean。泛型必须正确:
ServiceImpl<UserMapper, User>
中UserMapper
必须继承BaseMapper<User>
。事务控制:
saveBatch
,removeBatchByIds
,updateBatchById
等批量方法自带事务- 自定义方法如需事务,必须添加
@Transactional
不要重写
ServiceImpl
的核心方法(除非必要):
如save
,remove
,update
,避免破坏默认行为。与自动填充配合:
ServiceImpl
调用mapper.insert()
时会触发MetaObjectHandler
,确保配置正确。空值处理:
getById(null)
返回null
removeById(null)
不执行删除
五、使用技巧
技巧 1:链式查询(Query Chain)
// 条件查询
List<User> users = this.lambdaQuery()
.eq(User::getStatus, 1)
.gt(User::getAge, 18)
.like(User::getName, "张")
.orderByDesc(User::getCreateTime)
.last("LIMIT 100") // 自定义 SQL 片段(慎用)
.list();
// 单条记录
User user = this.lambdaQuery()
.eq(User::getEmail, "zhang@email.com")
.one(); // 返回单条,多条抛异常
// 存在性判断
boolean exists = this.lambdaQuery()
.eq(User::getName, "张三")
.exists();
技巧 2:分页查询
public Page<User> getUsersPage(int pageNum, int pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);
return this.page(page, new LambdaQueryWrapper<User>().eq(User::getStatus, 1));
}
技巧 3:获取 BaseMapper
// 在 ServiceImpl 中可通过 this.getBaseMapper() 获取
UserMapper mapper = this.getBaseMapper();
List<User> customList = mapper.selectCustomMethod(); // 调用自定义 Mapper 方法
技巧 4:条件构造器复用
private LambdaQueryWrapper<User> getActiveUserWrapper() {
return new LambdaQueryWrapper<User>().eq(User::getStatus, 1);
}
public long countActiveUsers() {
return this.count(getActiveUserWrapper());
}
public List<User> listActiveUsers() {
return this.list(getActiveUserWrapper());
}
六、最佳实践与性能优化
✅ 最佳实践
实践 | 说明 |
---|---|
接口继承 IService |
保持 API 一致性,便于替换实现 |
实现类继承 ServiceImpl |
复用通用逻辑,减少重复代码 |
业务方法命名清晰 | 如 disableUser , unlockAccount |
复杂逻辑封装在 Service | 避免 Controller 冗长 |
批量操作使用 saveBatch |
提升性能,减少数据库交互 |
⚡ 性能优化建议
合理使用批量操作
// ✅ 推荐:批量插入,每批 100~500 条 userService.saveBatch(users, 100); // ❌ 避免:循环单条插入 // for (User u : users) userService.save(u);
分页避免
COUNT(*)
全表扫描// 使用 MyBatis-Plus 分页插件,自动优化 COUNT 查询 Page<User> page = userService.page(new Page<>(1, 10), Wrappers.<User>lambdaQuery().eq(User::getStatus, 1));
避免 N+1 查询
// ❌ 错误:循环查数据库 // List<User> users = userService.list(); // for (User u : users) { // u.setOrders(orderService.listByUserId(u.getId())); // } // ✅ 正确:在 Mapper 层 JOIN 查询 或 使用缓存
缓存热点数据
@Cacheable(value = "user", key = "#id") public User getUserWithCache(Long id) { return this.getById(id); }
连接池配置
- 使用 HikariCP,
maximumPoolSize
根据并发调整(建议 20~50)
- 使用 HikariCP,
七、高级用法
场景 1:重写 ServiceImpl
方法(扩展功能)
@Slf4j
public class CustomServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {
@Override
public boolean save(T entity) {
log.info("准备保存: {}", entity);
boolean result = super.save(entity);
log.info("保存结果: {}, ID: {}", result,
((Entity)entity).getId()); // 假设实体有 getId()
return result;
}
}
// 使用
@Service
public class UserServiceImpl extends CustomServiceImpl<UserMapper, User> implements UserService {
// ...
}
场景 2:结合事件发布
@Autowired
private ApplicationEventPublisher eventPublisher;
@Override
@Transactional
public boolean save(User entity) {
boolean saved = super.save(entity);
if (saved) {
eventPublisher.publishEvent(new UserCreatedEvent(entity));
}
return saved;
}
八、验证是否生效
- 启动日志:查看 Spring 是否成功注入
UserServiceImpl
。 - 调用接口:测试
GET /api/user/1
是否返回数据。 - 数据库操作:插入数据后检查
createTime
是否由MetaObjectHandler
自动填充。 - 批量测试:插入 1000 条数据,验证
saveBatch
性能远优于循环插入。