跳到主要内容

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 校验

saveByDtoupdateByDto 自动执行 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() 等终结操作

📚 延伸阅读