Skip to content

增强 Service 层

BaseService 是对 MyBatis-Plus 原生 IService 的增强封装,提供了容错模式(Ignore)严格校验模式(Validate)两套 CRUD 方法,以及悲观锁、关联查询等高级功能。

核心特性

  • 双模式方法:Ignore(容错)和 Validate(严格校验)
  • 悲观锁支持getAndLock 系列方法支持 for update
  • 分页增强:自动按 updateTime 降序排序
  • 关联查询parseRelObject 简化一对一、一对多查询
  • 检查接口:集成 CheckProvider 提供业务检查能力

快速开始

1. 定义 Service 接口

java
import host.springboot.framework.mybatisplus.service.BaseService;

public interface UserService extends BaseService<User> {
    // 无需定义任何方法,继承 BaseService 即可
}

2. 定义 Service 实现类

java
import host.springboot.framework.mybatisplus.service.BaseServiceImpl;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> 
        implements UserService {
    // 无需实现任何方法
}

3. 使用示例

java
@RestController
@RequestMapping("/user")
public class UserController {
    
    private final UserService userService;
    
    // 容错模式 - 参数为 null 不抛异常
    @GetMapping("/{id}")
    public SingleVO<User> getById(@PathVariable Long id) {
        User user = userService.getByIdIgnore(id);  // 不存在返回 null
        return R.okSingle(user);
    }
    
    // 严格校验模式 - 参数为 null 抛异常
    @GetMapping("/validate/{id}")
    public SingleVO<User> getByIdValidate(@PathVariable Long id) {
        User user = userService.getByIdValidate(id);  // 不存在抛 MybatisServiceException
        return R.okSingle(user);
    }
}

方法体系

BaseService 提供了 3 大类方法

类别方法前缀/后缀特点适用场景
Ignore 模式xxxIgnore容错,null 不抛异常展示类查询、可选数据
Validate 模式xxxValidate严格,null 抛异常核心业务逻辑
QueryLock 模式getAndLockXxx悲观锁查询并发更新场景

Ignore 系列方法(容错模式)

存在性检查

方法参数返回值说明
isExistsByIdIgnore(id)Serializablebooleanid 为 null 返回 false
isExistsByIdIgnore(ids)Collectionbooleanids 为 null/empty 返回 false
countByIdIgnore(id)Serializablelongid 为 null 返回 -1
countByIdIgnore(ids)Collectionlongids 为 null/empty 返回 -1

使用示例

java
// 检查用户是否存在(容错)
if (userService.isExistsByIdIgnore(userId)) {
    // 用户存在,执行业务逻辑
}

// 统计用户数量(容错)
long count = userService.countByIdIgnore(userId);  // 不存在返回 -1

查询方法

方法参数返回值说明
getByIdIgnore(id)SerializableT不存在返回 null
getByIdIgnoreOpt(id)SerializableOptional<T>返回 Optional
listByIdIgnore(ids)CollectionList<T>不存在返回空集合
listIgnore(wrapper)WrapperList<T>不存在返回空集合
pageIgnore(pageQuery)PageQueryPageable<List<T>>自动按 updateTime 降序

使用示例

java
// 根据 ID 查询(容错)
User user = userService.getByIdIgnore(1L);  // 不存在返回 null
if (user != null) {
    // 处理用户信息
}

// 使用 Optional(推荐)
userService.getByIdIgnoreOpt(1L).ifPresent(user -> {
    // 处理用户信息
});

// 批量查询(容错)
List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = userService.listByIdIgnore(ids);  // 部分不存在也不抛异常

// 分页查询(自动按 updateTime 降序)
PageQuery pageQuery = new PageQuery();
pageQuery.setPageNo(1L);
pageQuery.setPageSize(10L);
Pageable<List<User>> page = userService.pageIgnore(pageQuery);

保存方法

方法参数返回值说明
saveIgnore(entity)TBooleanentity 为 null 返回 null
saveIgnore(entities)Collection<T>booleanentities 为 null/empty 返回 false
saveIgnore(entities, batchSize)Collection<T>, intboolean批量保存,指定批次大小

使用示例

java
// 保存单个实体(容错)
User user = new User();
user.setUsername("zhangsan");
user.setEmail("zhangsan@example.com");
Boolean result = userService.saveIgnore(user);  // null 参数返回 null

// 批量保存(容错)
List<User> users = buildUsers();
boolean success = userService.saveIgnore(users, 100);  // 每批保存 100 条

更新方法

方法参数返回值说明
updateByIdIgnore(entity)Tbooleanentity 为 null 返回 false
updateByIdIgnore(entities)Collection<T>booleanentities 为 null/empty 返回 false
updateByIdIgnore(entities, batchSize)Collection<T>, intboolean批量更新,指定批次大小

使用示例

java
// 更新单个实体(容错)
User user = userService.getByIdIgnore(1L);
if (user != null) {
    user.setEmail("new_email@example.com");
    userService.updateByIdIgnore(user);
}

// 批量更新(容错)
List<User> users = userService.listByIdIgnore(ids);
users.forEach(u -> u.setStatus(1));
userService.updateByIdIgnore(users, 100);

删除方法

方法参数返回值说明
removeByIdIgnore(id)Serializablebooleanid 为 null 返回 false
removeByIdIgnore(ids)Collectionbooleanids 为 null/empty 返回 false

使用示例

java
// 删除单个实体(容错)
boolean success = userService.removeByIdIgnore(1L);

// 批量删除(容错)
List<Long> ids = Arrays.asList(1L, 2L, 3L);
userService.removeByIdIgnore(ids);

Validate 系列方法(严格校验模式)

存在性检查

方法参数返回值异常情况
isExistsByIdValidate(id)Serializablebooleanid 为 null 抛异常
isExistsByIdValidate(ids)Collectionbooleanids 为 null/empty 抛异常
countByIdValidate(id)Serializablelongid 为 null 抛异常
countByIdValidate(ids)Collectionlongids 为 null/empty 抛异常

使用示例

java
// 检查用户是否存在(严格校验)
try {
    boolean exists = userService.isExistsByIdValidate(userId);
    if (exists) {
        // 用户存在
    }
} catch (MybatisServiceException e) {
    // userId 为 null
    logger.error("用户ID不能为空", e);
}

// 统计用户数量(严格校验)
long count = userService.countByIdValidate(userId);  // null 直接抛异常

查询方法

方法参数返回值异常情况
getByIdValidate(id)SerializableTid 为 null 或不存在抛异常
listByIdValidate(ids)CollectionList<T>ids 为 null/empty 或数据不完整抛异常
listValidate(wrapper)WrapperList<T>查询结果为空抛异常
pageValidate(pageQuery)PageQueryPageable<List<T>>pageQuery 为 null 抛异常

使用示例

java
// 根据 ID 查询(严格校验)
User user = userService.getByIdValidate(userId);  // 不存在直接抛异常
user.setEmail("new_email@example.com");
userService.updateByIdValidate(user);

// 批量查询(严格校验)
List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = userService.listByIdValidate(ids);  // 必须全部存在,否则抛异常

// 分页查询(严格校验)
Pageable<List<User>> page = userService.pageValidate(pageQuery);  // null 抛异常

保存方法

方法参数返回值异常情况
saveValidate(entity)Tbooleanentity 为 null 抛异常
saveValidate(entities)Collection<T>booleanentities 为 null/empty 抛异常
saveValidate(entities, batchSize)Collection<T>, intbooleanentities 为 null/empty 抛异常

使用示例

java
// 保存单个实体(严格校验)
User user = buildUser();
userService.saveValidate(user);  // null 直接抛异常

// 批量保存(严格校验)
List<User> users = buildUsers();
userService.saveValidate(users, 100);  // 空集合直接抛异常

更新方法

方法参数返回值异常情况
updateByIdValidate(entity)Tbooleanentity 为 null 或 ID 不存在抛异常
updateByIdValidate(entities)Collection<T>booleanentities 为 null/empty 或 ID 不存在抛异常

使用示例

java
// 更新单个实体(严格校验)
User user = userService.getByIdValidate(userId);
user.setEmail("new_email@example.com");
userService.updateByIdValidate(user);  // ID 不存在直接抛异常

// 批量更新(严格校验)
List<User> users = userService.listByIdValidate(ids);
users.forEach(u -> u.setStatus(1));
userService.updateByIdValidate(users);  // ID 不完整直接抛异常

删除方法

方法参数返回值异常情况
removeByIdValidate(id)Serializablebooleanid 为 null 或不存在抛异常
removeByIdValidate(ids)Collectionbooleanids 为 null/empty 或不存在抛异常

使用示例

java
// 删除单个实体(严格校验)
userService.removeByIdValidate(userId);  // 不存在直接抛异常

// 批量删除(严格校验)
userService.removeByIdValidate(ids);  // 任一 ID 不存在直接抛异常

QueryLock 系列方法(悲观锁)

使用 for update 实现悲观锁,适用于并发更新场景。

方法参数返回值说明
getAndLockIgnore(id)SerializableT容错模式
getAndLockValidate(id)SerializableT严格校验模式
getAndLockIgnore(ids)CollectionCollection<T>批量容错模式
getAndLockValidate(ids)CollectionCollection<T>批量严格校验模式

使用场景

场景1:库存扣减

java
@Transactional(rollbackFor = Exception.class)
public void deductStock(Long productId, Integer quantity) {
    // 锁定商品记录(for update)
    Product product = productService.getAndLockValidate(productId);
    
    // 检查库存
    if (product.getStock() < quantity) {
        throw new BusinessException("库存不足");
    }
    
    // 扣减库存
    product.setStock(product.getStock() - quantity);
    productService.updateByIdValidate(product);
}

场景2:余额扣除

java
@Transactional(rollbackFor = Exception.class)
public void deductBalance(Long userId, BigDecimal amount) {
    // 锁定用户记录
    User user = userService.getAndLockValidate(userId);
    
    // 检查余额
    if (user.getBalance().compareTo(amount) < 0) {
        throw new BusinessException("余额不足");
    }
    
    // 扣除余额
    user.setBalance(user.getBalance().subtract(amount));
    userService.updateByIdValidate(user);
}

生成的 SQL

sql
SELECT * FROM sys_user WHERE id = 1 FOR UPDATE;

注意

  • 必须在事务中使用,否则锁立即释放
  • 会阻塞其他事务对相同数据的 for update 查询
  • 慎用,可能导致性能问题

工具方法

clearEntityDefaultParameterAndGet

清除实体类的默认参数值,用于数据复制场景。

java
// 清除 createTime 和 updateTime
User newUser = BaseService.clearEntityDefaultParameterAndGet(oldUser);

// 自定义清除字段
User newUser = BaseService.clearEntityDefaultParameterAndGet(
    oldUser, 
    true,   // 清除 ID
    true,   // 清除 createTime
    true    // 清除 updateTime
);

toUnderlineCase

驼峰转下划线。

java
String columnName = BaseService.toUnderlineCase("userName");  // "user_name"

Ignore vs Validate 对比

对比项Ignore 模式Validate 模式
参数为 null返回默认值(null/false/-1)抛出 MybatisServiceException
数据不存在返回默认值抛出 MybatisServiceException
性能略优(少一次校验)略差(多一次校验)
适用场景展示类查询、可选数据核心业务逻辑
代码风格需要 null 检查无需 null 检查

使用建议

✅ 使用 Ignore 的场景

java
// 1. 前端展示(数据可选)
User user = userService.getByIdIgnore(userId);
if (user != null) {
    // 展示用户信息
}

// 2. 批量查询(部分数据不存在)
List<User> users = userService.listByIdIgnore(ids);  // 容忍部分不存在

// 3. 数据统计(可选条件)
long count = userService.countByIdIgnore(userId);

✅ 使用 Validate 的场景

java
// 1. 核心业务逻辑(数据必须存在)
User user = userService.getByIdValidate(userId);
user.setBalance(user.getBalance().subtract(amount));
userService.updateByIdValidate(user);

// 2. 数据完整性要求高
List<User> users = userService.listByIdValidate(ids);  // 必须全部存在

// 3. 关联数据校验
Order order = orderService.getByIdValidate(orderId);
Product product = productService.getByIdValidate(order.getProductId());

异常处理

Validate 系列方法会抛出 MybatisServiceException,建议使用全局异常处理器统一处理:

java
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MybatisServiceException.class)
    public BaseVO handleMybatisServiceException(MybatisServiceException e) {
        log.error("数据库操作异常", e);
        return R.base(e.getErrorCode(), e.getErrorMessage(), e.getUserTip());
    }
}

最佳实践

✅ 推荐做法

  1. Service 接口继承 BaseService

    java
    public interface UserService extends BaseService<User> { }  // ✅
  2. 根据场景选择模式

    java
    // 展示类查询使用 Ignore
    User user = userService.getByIdIgnore(userId);  // ✅
    
    // 核心业务使用 Validate
    User user = userService.getByIdValidate(userId);  // ✅
  3. 悲观锁必须在事务中

    java
    @Transactional  // ✅
    public void deductStock(Long productId, Integer quantity) {
        Product product = productService.getAndLockValidate(productId);
        // ...
    }
  4. 使用 Optional 优雅处理 null

    java
    userService.getByIdIgnoreOpt(userId).ifPresent(user -> {  // ✅
        // 处理用户
    });

❌ 不推荐做法

  1. ❌ 不继承 BaseService

    java
    public interface UserService extends IService<User> { }  // ❌
  2. ❌ 滥用 Validate 模式

    java
    // 前端展示不应该用 Validate
    User user = userService.getByIdValidate(userId);  // ❌ 可能抛异常影响体验
  3. ❌ 悲观锁不在事务中

    java
    public void deductStock(Long productId, Integer quantity) {  // ❌ 没有 @Transactional
        Product product = productService.getAndLockValidate(productId);
        // 锁立即释放,无效
    }
  4. ❌ 混用 Ignore 和 Validate

    java
    User user = userService.getByIdIgnore(userId);  // ❌
    if (user != null) {
        userService.updateByIdValidate(user);  // ❌ 风格不统一
    }

常见问题

Q: Ignore 和 Validate 如何选择?

A: 简单判断标准

  • 数据可选 → 用 Ignore
  • 数据必须存在 → 用 Validate

举例

java
// 展示用户头像(可选)
userService.getByIdIgnore(userId);  // ✅ 不存在就不显示

// 扣除用户余额(必须存在)
userService.getByIdValidate(userId);  // ✅ 不存在就是异常

Q: 为什么 Validate 方法要抛异常?

A: 设计理念:让异常在调用处立即暴露,避免 null 传播导致的 NPE。

对比

java
// Ignore - 需要 null 检查
User user = userService.getByIdIgnore(userId);
if (user == null) {
    throw new BusinessException("用户不存在");
}
user.setEmail(newEmail);

// Validate - 无需 null 检查
User user = userService.getByIdValidate(userId);  // 不存在直接抛异常
user.setEmail(newEmail);  // 确保 user 不为 null

Q: getAndLock 什么时候用?

A: 适用场景

  1. 库存扣减:防止超卖
  2. 余额扣除:防止透支
  3. 乐观锁失败后:降级为悲观锁

不适用场景

  • 高并发读多写少(用乐观锁)
  • 不需要事务一致性

Q: pageIgnore 为什么自动按 updateTime 排序?

A: 设计理念:大部分列表查询都希望看到最新修改的数据。

自定义排序

java
// 使用 IPage 自定义排序
Page<User> page = new Page<>(1, 10);
page.addOrder(OrderItem.asc("username"));  // 按用户名升序
Pageable<List<User>> result = userService.pageIgnore(page);

Q: BaseService 和 IService 的关系?

A: BaseService 继承 IService,所以:

  • MyBatis-Plus 原生方法都可以用
  • 额外提供了 Ignore/Validate/QueryLock 等增强方法
java
userService.save(user);           // ✅ IService 原生方法
userService.saveIgnore(user);     // ✅ BaseService 增强方法
userService.getById(1L);          // ✅ IService 原生方法
userService.getByIdIgnore(1L);    // ✅ BaseService 增强方法

下一步

扩展阅读

Released under the Apache-2.0 License