Optional 实战:博客项目查询层重构(AI 辅助学习 Java 8)
前两篇学了 Optional 基础和进阶。今天在一个完整的 Service 层场景中,用 Optional 重构所有查询、更新、删除逻辑,告别 if-null 判断。
一、Repository 层返回 Optional
Spring Data JPA 的 findById() 本身就返回 Optional<T>。这是我们 Service 层的起点。
public interface BlogRepository extends JpaRepository<Blog, Long> {
// findById(Long id) -> Optional<Blog> 已内置
}
二、根据 ID 查询
重构前
public String getBlogTitle(Long id) {
Blog blog = repository.findById(id).orElse(null);
if (blog == null) {
return "博客不存在";
}
return blog.getTitle();
}
重构后
public String getBlogTitle(Long id) {
return repository.findById(id)
.map(Blog::getTitle)
.orElse("博客 [id=" + id + "] 不存在");
}
三、更新操作
重构前
public String updateBlog(Long id, String title, String content) {
Blog blog = repository.findById(id).orElse(null);
if (blog == null) {
return "更新失败: 博客不存在";
}
blog.setTitle(title);
blog.setContent(content);
repository.save(blog);
return "更新成功: " + blog.getTitle();
}
重构后
public String updateBlog(Long id, String title, String content) {
return repository.findById(id)
.map(blog -> {
blog.setTitle(title);
blog.setContent(content);
repository.save(blog);
return "更新成功: " + blog.getTitle();
})
.orElse("更新失败: 博客 [id=" + id + "] 不存在");
}
map 内部:有值时执行更新并返回成功消息;无值时跳过 map,走到 orElse 返回失败。
四、删除操作
重构前
public String deleteBlog(Long id) {
if (!repository.existsById(id)) {
return "删除失败: 博客不存在";
}
repository.deleteById(id);
return "删除成功";
}
重构后
public String deleteBlog(Long id) {
return repository.findById(id)
.map(blog -> {
repository.delete(blog);
return "删除成功: " + blog.getTitle();
})
.orElse("删除失败: 博客 [id=" + id + "] 不存在");
先查再删,一次数据库操作。如果不存在,自然走到 orElse。
五、统计查询
最高阅读量的文章
public Optional<Blog> getMostViewedBlog() {
return repository.findAll().stream()
.max(Comparator.comparingInt(Blog::getViews));
}
// 调用方
service.getMostViewedBlog()
.map(b -> b.getTitle() + " (" + b.getViews() + " 阅读)")
.ifPresent(System.out::println);
返回 Optional<Blog>,调用方自行决定如何处理空的情况。
分类统计
public Map<String, Long> countByCategory() {
return repository.findAll().stream()
.collect(Collectors.groupingBy(Blog::getCategory, Collectors.counting()));
}
六、运行结果
=== 根据 ID 查询 ===
id=1: Lambda 入门
id=99: 博客不存在
=== 更新博客 ===
id=2 更新: 更新成功: Stream API 完全指南
id=99 更新: 更新失败: 博客不存在
=== 删除博客 ===
id=3 删除: 删除成功: Vue3 API
id=99 删除: 删除失败: 博客不存在
=== 统计 ===
分类统计: {Java=2, 前端=2, 运维=1}
最高阅读: React Hooks (400 阅读)
七、本周学习总结
第五周我们学习了 Optional,完整路线:
周二:Optional 基础——of/map/flatMap/orElse/ifPresent
周四:Optional 进阶——filter/ifPresent/Stream 结合
周六:Optional 实战——Service 层查询更新删除全面重构(本篇)
核心收获:
| 知识点 | 方法 | 一句话 |
|---|---|---|
| 创建 | of/ofNullable/empty | 包装可能为空的值 |
| 转换 | map | 有值时转换 |
| 展平 | flatMap | 转换返回 Optional 时避免嵌套 |
| 过滤 | filter | 满足条件才保留 |
| 默认值 | orElse/orElseGet | 为空时提供备选 |
| 异常 | orElseThrow | 为空时抛异常 |
| 消费 | ifPresent | 有值时执行 |
关键原则:
- Optional 设计为返回值,不是字段类型
- 不要用
get()直接取值,用orElse/ifPresent替代 - 默认值有开销时用
orElseGet替代orElse
八、下周预告
第六周进入 Java 8 新时间 API,彻底告别 Date/Calendar 的坑。
系列文章导航:
更多请关注我的微信公众号,感谢!