一、核心概念
- 服务层扩展点
IService
:MP 提供的服务层接口(含 17+ 内置方法)ServiceImpl
:服务层基础实现类
- 扩展类型
- 自定义 CRUD 方法
- 复杂业务逻辑封装
- 跨表事务操作
- 批量处理优化
- 核心注解
@Service
:声明 Spring 服务@DS
:多数据源路由(需配合 dynamic-datasource)@Transactional
:事务控制
二、详细操作步骤
1. 基础服务接口定义
// 基础接口继承 IService
public interface UserService extends IService<User> {
// 此处声明自定义方法
}
2. 服务实现类扩展
@Service
public class UserServiceImpl
extends ServiceImpl<UserMapper, User> // 继承MP基础实现
implements UserService { // 实现自定义接口
// 示例1:带业务逻辑的查询
@Override
public List<User> findActiveUsers() {
return lambdaQuery()
.eq(User::getStatus, 1)
.gt(User::getLoginCount, 0)
.orderByDesc(User::getLastLoginTime)
.list();
}
// 示例2:跨表更新(事务操作)
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateUserWithLog(User user, String operation) {
// 更新用户
boolean updateSuccess = updateById(user);
// 插入日志
UserLog log = new UserLog()
.setUserId(user.getId())
.setOperation(operation);
return updateSuccess && userLogService.save(log);
}
// 示例3:批量处理扩展
@Override
public boolean batchUpdateStatus(List<Long> ids, int status) {
if (CollectionUtils.isEmpty(ids)) return false;
return update(new LambdaUpdateWrapper<User>()
.set(User::getStatus, status)
.in(User::getId, ids)
);
}
}
3. 复杂分页查询扩展
// 接口中定义
IPage<UserVO> pageUserWithDept(Page<User> page, UserQueryDTO query);
// 实现类
@Override
public IPage<UserVO> pageUserWithDept(Page<User> page, UserQueryDTO query) {
return baseMapper.selectUserPage(
page,
Wrappers.<User>lambdaQuery()
.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
.eq(query.getDeptId() != null, User::getDeptId, query.getDeptId())
);
}
// Mapper中实现
@Select("SELECT u.*, d.name AS dept_name FROM user u LEFT JOIN dept d ON u.dept_id = d.id ${ew.customSqlSegment}")
IPage<UserVO> selectUserPage(IPage<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);
4. 多数据源方法扩展
// 指定数据源操作
@DS("slave") // 从库查询
@Override
public User getFromReplica(Long id) {
return getById(id);
}
@DS("master") // 主库写入
@Transactional
@Override
public boolean criticalUpdate(User user) {
return updateById(user);
}
5. 流式查询扩展
@Override
public void processLargeData(Consumer<User> processor) {
try (Cursor<User> cursor = baseMapper.selectCursor(
Wrappers.<User>lambdaQuery().gt(User::getId, 10000)
)) {
cursor.forEach(processor);
}
}
三、常见错误与解决
事务失效
- 错误:非 public 方法添加
@Transactional
- 解决:确保方法为 public 且类被 Spring 代理
- 错误:非 public 方法添加
NPE异常
- 错误:未判空直接调用
baseMapper
方法 - 解决:添加空值检查
public User safeGetById(Long id) { return id != null ? getById(id) : null; }
- 错误:未判空直接调用
批量操作性能差
- 错误:循环中单条更新
- 解决:使用 MP 批量方法 + 批处理配置
mybatis-plus: global-config: db-config: batch-size: 1000
多数据源切换失败
- 错误:同类内方法调用导致注解失效
- 解决:通过
AopContext.currentProxy()
调用
四、注意事项
方法命名规范
- 查询:
findXxx
/getXxx
/listXxx
- 更新:
updateXxx
/modifyXxx
- 删除:
removeXxx
/deleteXxx
- 插入:
saveXxx
/createXxx
- 查询:
事务边界控制
- 只读方法:
@Transactional(readOnly = true)
- 写操作:明确指定回滚异常
- 避免大事务:拆分业务逻辑
- 只读方法:
异常处理原则
- 服务层捕获所有持久层异常
- 转换为业务异常向上抛出
try { return baseMapper.selectById(id); } catch (Exception e) { throw new BusinessException("用户查询失败", e); }
五、使用技巧
链式条件封装
public LambdaQueryWrapper<User> buildQuery(UserQuery query) { return new LambdaQueryWrapper<User>() .like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName()) .between(query.getStartTime() != null && query.getEndTime() != null, User::getCreateTime, query.getStartTime(), query.getEndTime()) .eq(query.getStatus() != null, User::getStatus, query.getStatus()); }
通用结果包装
public <T> Result<T> successResult(T data) { return Result.success(data); } public Result<Void> failResult(String msg) { return Result.fail(msg); }
多租户自动注入
@Override public boolean save(User entity) { if (entity.getTenantId() == null) { entity.setTenantId(TenantContext.getCurrentId()); } return super.save(entity); }
方法级权限控制
@PreAuthorize("hasRole('ADMIN')") @Override public boolean deleteUser(Long id) { return removeById(id); }
六、最佳实践与性能优化
批量操作优化
@Transactional public boolean batchInsert(List<User> users) { // 分批次插入(每批1000条) return saveBatch(users, 1000); } // JDBC批处理模式 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new BatchInsertInnerInterceptor()); return interceptor; }
二级缓存应用
@Cacheable(value = "userCache", key = "#id") @Override public User getByIdWithCache(Long id) { return getById(id); } @CacheEvict(value = "userCache", key = "#user.id") @Override public boolean updateUser(User user) { return updateById(user); }
读写分离配置
@DS("slave") @Override public List<User> readOnlyQuery() { return list(); } @DS("master") @Transactional @Override public boolean writeOperation() { // ... }
分页性能优化
public Page<User> optimizePageQuery(PageQuery query) { Page<User> page = new Page<>(query.getPage(), query.getSize()); page.setSearchCount(false); // 不执行COUNT查询 List<User> records = baseMapper.selectPageWithoutCount(page, buildQuery(query)); return page.setRecords(records); } // Mapper中使用覆盖索引 @Select("SELECT id,name FROM user ${ew.customSqlSegment}") List<User> selectPageWithoutCount(Page<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);
异步处理
@Async("taskExecutor") @Override public CompletableFuture<List<User>> asyncProcess() { return CompletableFuture.completedFuture(findActiveUsers()); }
**七、复杂场景综合示例
场景:用户积分批量更新
@Transactional(rollbackFor = Exception.class)
public boolean batchUpdatePoints(List<PointUpdateDTO> updates) {
// 1. 校验数据
if (CollectionUtils.isEmpty(updates)) return false;
// 2. 分组处理(每500条一批)
Map<Boolean, List<PointUpdateDTO>> groups = updates.stream()
.collect(Collectors.partitioningBy(dto -> dto.getDelta() > 0));
// 3. 增加积分
processPointGroup(groups.get(true), true);
// 4. 扣除积分
processPointGroup(groups.get(false), false);
// 5. 记录日志
return pointLogService.saveBatch(updates.stream()
.map(dto -> new PointLog(dto.getUserId(), dto.getDelta()))
.collect(Collectors.toList()));
}
private void processPointGroup(List<PointUpdateDTO> list, boolean isAdd) {
if (CollectionUtils.isEmpty(list)) return;
List<Long> userIds = list.stream()
.map(PointUpdateDTO::getUserId)
.collect(Collectors.toList());
// 批量更新语句
String operation = isAdd ? "+" : "-";
String sql = String.format(
"UPDATE user SET points = points %s {delta} WHERE id = {id} AND points %s {delta} >= 0",
operation, isAdd ? ">=" : ""
);
// 执行批量更新
executeBatch(userIds, (sqlSession, id) -> {
PointUpdateDTO dto = findDtoById(list, id);
sqlSession.update(sql, Map.of("id", id, "delta", Math.abs(dto.getDelta())));
});
}
八、调试与监控
SQL 执行监控
public List<User> monitoredQuery() { StopWatch watch = new StopWatch(); watch.start("query"); List<User> users = findActiveUsers(); watch.stop(); if (watch.getTotalTimeMillis() > 500) { log.warn("慢查询: {}", watch.prettyPrint()); } return users; }
Arthas 诊断
# 监控方法调用 watch com.example.service.UserService * '{params, returnObj}' -x 3
性能分析插件
@Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor interceptor = new PerformanceInterceptor(); interceptor.setMaxTime(200); // 阈值(ms) interceptor.setFormat(true); // 格式化SQL return interceptor; }
关键总结
扩展原则
- 基础 CRUD 用内置方法
- 业务逻辑封装在服务层
- 复杂操作拆分为原子方法
性能铁律
- 批量操作配置批处理大小
- 分页查询禁用不必要 COUNT
- 大结果集使用流式处理
事务准则
- 只读操作用
readOnly=true
- 写操作明确回滚规则
- 事务内避免远程调用
- 只读操作用
扩展模式
graph LR A[Controller] --> B[Service接口] B --> C[自定义方法] C --> D[ServiceImpl实现] D --> E[基础CRUD] D --> F[Mapper扩展] D --> G[其他Service]
通过合理扩展服务层方法,可实现:
- 业务逻辑高内聚
- 代码复用最大化
- 性能瓶颈可优化
- 系统架构更清晰