Optional 实战:博客项目查询层重构(AI 辅助学习 Java 8)

23 阅读3分钟

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 的坑。


系列文章导航

更多请关注我的微信公众号,感谢!

微信图片_20260427132414_18_737.jpg