Base Framework
blog-common 模块提供了基于 MyBatis-Plus 的增强型 Base Framework,简化 CRUD 开发,规范代码结构。
🧩 核心组件
| 组件 | 用途 |
|---|---|
IBaseService<E, V, D> | 通用业务接口定义 |
BaseServiceImpl<M, E, V, D, C> | 接口默认实现,集成 MapStruct |
BaseConverter<D, E, V> | DTO/Entity/VO 转换契约 |
Identifiable<T> | 标记 DTO 拥有主键 ID |
🚀 快速开始
Step 1: 定义 DTO
@Data
public class RoleDTO implements Identifiable<Long> {
private Long id;
private String roleName;
private String roleKey;
}
关键约束
DTO 必须实现 Identifiable 接口,以便 BaseService 自动提取 ID。
Step 2: 定义 Converter
@Mapper(
componentModel = "spring",
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
)
public interface RoleConverter extends BaseConverter<RoleDTO, Role, RoleVO> {
}
关键配置
nullValuePropertyMappingStrategy = IGNORE 是必须的!否则更新时会用 null 覆盖已有数据。
Step 3: 定义 Service
// 接口
public interface IRoleService extends IBaseService<Role, RoleVO, RoleDTO> {
void assignRoleToUser(Long roleId, Long userId);
}
// 实现
@Service
@RequiredArgsConstructor
public class RoleServiceImpl
extends BaseServiceImpl<RoleMapper, Role, RoleVO, RoleDTO, RoleConverter>
implements IRoleService {
@Override
protected void preSave(Role entity) {
// 钩子:保存前自动设置默认值
if (entity.getStatus() == null) {
entity.setStatus(1);
}
}
}
Step 4: Controller 调用
@RestController
@RequestMapping("/api/v1/roles")
@RequiredArgsConstructor
public class RoleController {
private final IRoleService roleService;
@GetMapping("/{id}")
public Result<RoleVO> getInfo(@PathVariable Long id) {
return roleService.getVoById(id)
.map(Result::success)
.orElseThrow(() -> new BusinessException(SystemErrorCode.NOT_FOUND));
}
@PostMapping
public Result<Long> add(@Valid @RequestBody RoleDTO dto) {
// 自动:DTO校验 → Entity转换 → preSave钩子 → 保存
return Result.success((Long) roleService.saveByDto(dto));
}
@PutMapping
public Result<Boolean> edit(@Valid @RequestBody RoleDTO dto) {
// 自动:查旧数据 → 增量更新 → preUpdate钩子 → 保存
return Result.success(roleService.updateByDto(dto));
}
}
⚠️ 重要注意事项
更新操作对比
| 方法 | 安全性 | 说明 |
|---|---|---|
updateByDto | ✅ 安全 | 先查询旧数据,增量合并 DTO |
updateBatchByDto | ⚠️ 不安全 | 直接转换,不加载原数据 |
// updateByDto 内部流程:
E entity = this.getById(id); // 1. 查询原数据
converter.updateEntityFromDto(dto, entity); // 2. 增量合并
this.updateById(entity); // 3. 保存
自动 DTO 校验
saveByDto 和 updateByDto 自动执行 JSR-303 校验:
@Data
public class UserDTO implements Identifiable<Long> {
@NotBlank(message = "用户名不能为空")
private String username;
@Email
private String email;
}
// 校验失败会抛出 BusinessException(SystemErrorCode.PARAM_ERROR)
userService.saveByDto(userDTO);
删除操作幂等性
removeById 遵循 RESTful 幂等原则:
// 即使资源不存在,也返回 true(表示"已不存在"状态达成)
roleService.removeById(999L); // → true
// 如需确认实际删除,先检查存在性
if (roleService.getById(id) != null) {
roleService.removeById(id);
}
生命周期钩子
@Override
protected void preSave(Role entity) {
// 新增前:设置默认值、生成编号等
}
@Override
protected void preUpdate(Role entity) {
// 更新前:记录变更、校验状态等
}
🌊 流式查询(大数据量)
适用于导出大量数据,避免 OOM:
Mapper 层
@Select("SELECT * FROM sys_user ${ew.customSqlSegment}")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
Stream<User> streamList(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
Service 层
@Override
@Transactional(readOnly = true)
public void streamVo(Wrapper<User> wrapper, StreamProcessor<UserVO> processor) {
try (Stream<User> stream = baseMapper.streamList(wrapper)) {
processor.process(stream.map(converter::entityToVo));
}
}
使用示例
userService.streamVo(null, voStream -> {
voStream.forEach(csvWriter::write); // 逐条写入,不占内存
});
注意事项
- 必须在事务内执行(
@Transactional) - 必须使用
try-with-resources关闭 Stream - 禁止调用
collect()等终结操作
📚 延伸阅读
- 开发规范 — 编码标准
- MapStruct 指南 — 对象映射最佳实践