60. 【Java】从零到一:个人博客项目(上)

摘要: 本文是个人博客项目的上篇,详细讲解如何使用Spring Boot 3.4、JPA、PostgreSQL和Thymeleaf等技术栈搭建博客系统的后端基础。内容涵盖需求分析、技术选型、数据库设计、项目初始化、实体层和Repository层的完整实现,并提供了可运行的代码示例和初始化数据脚本,帮助Java开发者快速掌握全栈项目开发的核心流程。

关键词: Spring Boot, JPA, PostgreSQL, 个人博客系统, 全栈开发


经过前面五十九篇文章的积累,你已经从“Java 小白”成长为了一名掌握了 Java 核心语法、面向对象、集合框架、I/O、并发编程、网络编程、单元测试、日志、JDBC、Spring Boot 的开发者。可以说,你已经有了一个相当完整的 Java 技能树。

但技能树再茂盛,如果不结出果实,也只是好看的装饰。今天,我们要做的事就是把之前学到的所有知识——Spring Boot、JPA、数据库、Maven、日志、单元测试——全部放到一个真正的项目中,从零开始构建一个完整的个人博客系统

这个项目将分为两篇文章:

  • 上篇(今天):需求分析、技术选型、数据库设计、项目初始化、实体层和 Repository 层。
  • 下篇(下一篇):业务逻辑层(Service)、控制器层(Controller)、前端页面、安全控制、项目打包与部署。

今天,我们就先搭好整个项目的“地基”。


1. 需求分析 —— 一个博客应该有哪些功能?

在写代码之前,我们先把需求想清楚。一个个人博客,最核心的功能当然是写文章和看文章。但为了让项目更丰满,我们给它加上分类、标签、评论和用户认证。

功能列表

模块功能说明
文章管理发布文章管理员可以写新文章,包含标题、内容、分类、标签
编辑文章修改已发布的文章
删除文章删除文章(软删除或硬删除)
查看文章列表分页展示所有文章,按发布时间倒序
查看文章详情点击标题进入文章详情页,显示完整内容
按分类筛选点击某个分类,只显示该分类下的文章
按标签筛选点击某个标签,只显示包含该标签的文章
搜索文章按标题关键词搜索
分类管理增删改查管理博客的分类(如“Java”、“生活”、“思考”)
标签管理增删改查管理博客的标签(如“Spring Boot”、“读书笔记”)
评论管理发表评论访客在文章下方发表评论
审核评论管理员审核评论(为简化,我们直接显示,不加审核)
用户认证登录管理员登录后才能进入后台管理
注销退出登录

为了让项目在合理的范围内完成,我们会做一些简化:

  • 只有管理员(一个用户)能写文章、管理分类和标签。
  • 评论不需要审核,直接显示。
  • 暂时不提供注册功能(管理员账号预先在数据库中创建)。

2. 技术选型 —— 用什么工具来搭建?

层次技术说明
后端框架Spring Boot 3.4当前最新稳定版,支持 JDK 21
ORM 框架Spring Data JPA (Hibernate)简化数据库操作,用注解映射实体
数据库PostgreSQL关系型数据库,存储所有数据
模板引擎Thymeleaf服务端渲染 HTML 模板
前端样式Bootstrap 5 + 原生 CSS快速搭建美观的界面
构建工具Maven依赖管理和构建
日志SLF4J + Logback项目日志记录
测试JUnit 5 + Spring Boot Test单元测试和集成测试

注:我们选择 Thymeleaf 作为模板引擎,而不是前后端分离(Vue/React),是因为这个系列的目标是让你理解“全栈”的概念——从数据库到前端页面,你都能掌控。Thymeleaf 是 Spring Boot 官方推荐的模板引擎,语法简单,和 Spring 集成得天衣无缝。


3. 数据库设计 —— 画出数据的“蓝图”

在写代码之前,我们先设计好数据库的表结构。

3.1 ER 图(概念设计)

我们需要的表有:

  • t_user:用户表(管理员)
  • t_category:分类表
  • t_tag:标签表
  • t_article:文章表
  • t_comment:评论表
  • t_article_tag:文章-标签关联表(多对多)
3.2 详细的表结构

① 用户表(t_user)

字段名类型说明
idBIGINT主键,自增
usernameVARCHAR(50)用户名,唯一
passwordVARCHAR(100)密码(存储加密后的值)
emailVARCHAR(100)邮箱
avatarVARCHAR(200)头像 URL
created_atDATETIME创建时间

② 分类表(t_category)

字段名类型说明
idBIGINT主键,自增
nameVARCHAR(50)分类名称,唯一
slugVARCHAR(50)URL 友好别名(如 “java”)
created_atDATETIME创建时间

③ 标签表(t_tag)

字段名类型说明
idBIGINT主键,自增
nameVARCHAR(50)标签名称,唯一
created_atDATETIME创建时间

④ 文章表(t_article)

字段名类型说明
idBIGINT主键,自增
titleVARCHAR(200)文章标题
slugVARCHAR(200)URL 友好别名
summaryVARCHAR(500)文章摘要
contentLONGTEXT文章正文(Markdown 格式)
cover_imageVARCHAR(200)封面图片 URL
view_countINT浏览次数
category_idBIGINT外键,指向分类表
user_idBIGINT外键,指向用户表(作者)
statusTINYINT状态:0-草稿,1-已发布
is_topBOOLEAN是否置顶
created_atDATETIME创建时间
updated_atDATETIME更新时间
published_atDATETIME发布时间

⑤ 评论表(t_comment)

字段名类型说明
idBIGINT主键,自增
article_idBIGINT外键,指向文章表
author_nameVARCHAR(50)评论者姓名
author_emailVARCHAR(100)评论者邮箱
contentVARCHAR(1000)评论内容
created_atDATETIME创建时间
is_approvedBOOLEAN是否审核通过(本项目中默认 true)

⑥ 文章-标签关联表(t_article_tag)

字段名类型说明
article_idBIGINT外键,指向文章表
tag_idBIGINT外键,指向标签表

这是一个多对多关系,所以需要一张中间表。


4. 创建 Spring Boot 项目

4.1 在 Spring Initializr 中创建项目
  1. 访问 https://start.spring.io 或使用 IDEA 的 Spring Initializr。
  2. 填写项目信息:
    • Groupcom.example
    • Artifactblog
    • Nameblog
    • Description个人博客系统
    • Package namecom.example.blog
    • PackagingJar
    • Java21
  3. 添加依赖(在 Dependencies 中搜索并勾选):
    • Spring Web:Web 应用基础
    • Spring Data JPA:JPA 数据库访问
    • Thymeleaf:模板引擎
    • PostgreSQL Driver:PostgreSQL 驱动
    • Lombok:简化 getter/setter(可选)
    • Spring Boot DevTools:热加载(开发时方便)
    • Spring Security:用户认证(可选,我们后面会加)
  4. 点击 Generate 下载项目,解压后在 IDEA 中打开。
4.2 查看 pom.xml

关键的依赖部分应该长这样:

<dependencies>
    <!-- Web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Thymeleaf 模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    <!-- JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- PostgreSQL 驱动 -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
4.3 配置数据库连接

src/main/resources 目录下,把 application.properties 重命名为 application.yml(YAML 格式更清晰),然后写入:

# 服务器配置
server:
  port: 8080

# 数据库配置
spring:
  datasource:
    # PostgreSQL 连接 URL
    url: jdbc:postgresql://localhost:5432/blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: pgsql     # PostgreSQL 默认超级用户通常是 postgres
    password: pgsql     # 修改为你本地 PostgreSQL 的密码
    driver-class-name: org.postgresql.Driver

  # JPA 配置
  jpa:
    hibernate:
      ddl-auto: update   # 自动创建/更新表结构(开发阶段用 update,生产环境用 none)
    show-sql: true        # 控制台显示 SQL 语句
    properties:
      hibernate:
        format_sql: true  # 格式化 SQL 输出
        dialect: org.hibernate.dialect.PostgreSQLDialect

  # Thymeleaf 配置
  thymeleaf:
    cache: false          # 开发阶段关闭模板缓存,方便修改立即生效
    encoding: UTF-8
    mode: HTML

  # 日志配置
logging:
  level:
    com.example.blog: DEBUG
    org.springframework.web: INFO

在 PostgreSQL 中创建数据库:

CREATE DATABASE IF NOT EXISTS blog;

5. 创建实体类(Entity)

com.example.blog.entity 包下创建所有实体类。我们使用 JPA 注解来映射表和字段。

5.1 基础实体类(BaseEntity)

为了统一管理 created_atupdated_at,我们创建一个基类:

package com.example.blog.entity;

import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @CreationTimestamp
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @UpdateTimestamp
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}
  • @MappedSuperclass:这个类不会被映射成数据库表,但它的字段会被子类继承。
  • @CreationTimestamp:Hibernate 会在插入时自动设置时间。
  • @UpdateTimestamp:Hibernate 会在更新时自动更新时间。
5.2 用户实体(User)
package com.example.blog.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.ArrayList;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "t_user")
public class User extends BaseEntity {

    @Column(nullable = false, unique = true, length = 50)
    private String username;

    @Column(nullable = false, length = 100)
    private String password;

    @Column(length = 100)
    private String email;

    @Column(length = 200)
    private String avatar;

    // 一个用户可以发布多篇文章
    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Article> articles = new ArrayList<>();
}
5.3 分类实体(Category)
package com.example.blog.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.ArrayList;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "t_category")
public class Category extends BaseEntity {

    @Column(nullable = false, unique = true, length = 50)
    private String name;

    @Column(unique = true, length = 50)
    private String slug;   // URL 友好的别名,如 "java"

    // 一个分类下有多篇文章
    @OneToMany(mappedBy = "category", fetch = FetchType.LAZY)
    private List<Article> articles = new ArrayList<>();
}
5.4 标签实体(Tag)
package com.example.blog.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.ArrayList;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "t_tag")
public class Tag extends BaseEntity {

    @Column(nullable = false, unique = true, length = 50)
    private String name;

    // 多对多关系:一个标签可以属于多篇文章
    @ManyToMany(mappedBy = "tags")
    private List<Article> articles = new ArrayList<>();
}
5.5 文章实体(Article)—— 核心
package com.example.blog.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "t_article")
public class Article extends BaseEntity {

    @Column(nullable = false, length = 200)
    private String title;

    @Column(unique = true, length = 200)
    private String slug;

    @Column(length = 500)
    private String summary;

    @Column(columnDefinition = "TEXT")
    private String content;

    @Column(name = "cover_image", length = 200)
    private String coverImage;

    @Column(name = "view_count")
    private Integer viewCount = 0;

    @Column(name = "status")
    private Integer status = 1;  // 0-草稿,1-已发布

    @Column(name = "is_top")
    private Boolean isTop = false;

    @Column(name = "published_at")
    private LocalDateTime publishedAt;

    // ---- 关联关系 ----

    // 多对一:一篇文章属于一个分类
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;

    // 多对一:一篇文章属于一个作者
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User author;

    // 多对多:一篇文章可以有多个标签
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "t_article_tag",
        joinColumns = @JoinColumn(name = "article_id"),
        inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private List<Tag> tags = new ArrayList<>();

    // 一对多:一篇文章有多条评论
    @OneToMany(mappedBy = "article", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Comment> comments = new ArrayList<>();
}
5.6 评论实体(Comment)
package com.example.blog.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "t_comment")
public class Comment extends BaseEntity {

    @Column(name = "author_name", nullable = false, length = 50)
    private String authorName;

    @Column(name = "author_email", length = 100)
    private String authorEmail;

    @Column(nullable = false, length = 1000)
    private String content;

    @Column(name = "is_approved")
    private Boolean isApproved = true;

    // 多对一:一条评论属于一篇文章
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "article_id")
    private Article article;
}

6. 创建 Repository 接口

在 JPA 中,Repository 接口负责数据访问。我们不需要写实现类——Spring Data JPA 会在运行时自动生成。

com.example.blog.repository 包下创建:

package com.example.blog.repository;

import com.example.blog.entity.Article;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {

    // 根据 slug 查询文章(用于 URL 访问)
    Optional<Article> findBySlug(String slug);

    // 分页查询已发布的文章(按发布时间倒序)
    Page<Article> findByStatusOrderByPublishedAtDesc(Integer status, Pageable pageable);

    // 查询某个分类下的已发布文章(分页)
    Page<Article> findByCategoryIdAndStatusOrderByPublishedAtDesc(
        Long categoryId, Integer status, Pageable pageable
    );

    // 查询包含某个标签的已发布文章(分页)
    @Query("SELECT a FROM Article a JOIN a.tags t WHERE t.id = :tagId AND a.status = :status ORDER BY a.publishedAt DESC")
    Page<Article> findByTagIdAndStatus(@Param("tagId") Long tagId, @Param("status") Integer status, Pageable pageable);

    // 按标题关键词搜索已发布文章(分页)
    @Query("SELECT a FROM Article a WHERE a.status = :status AND a.title LIKE CONCAT('%', :keyword, '%') ORDER BY a.publishedAt DESC")
    Page<Article> searchByKeyword(@Param("keyword") String keyword, @Param("status") Integer status, Pageable pageable);

    // 获取置顶的文章
    List<Article> findByIsTopTrueAndStatusOrderByPublishedAtDesc(Integer status);

    // 统计某个分类下的文章数
    Long countByCategoryId(Long categoryId);
}

同样,创建其他 Repository:

// UserRepository.java
package com.example.blog.repository;

import com.example.blog.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

// CategoryRepository.java
package com.example.blog.repository;

import com.example.blog.entity.Category;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
    Optional<Category> findBySlug(String slug);
    Optional<Category> findByName(String name);
}

// TagRepository.java
package com.example.blog.repository;

import com.example.blog.entity.Tag;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface TagRepository extends JpaRepository<Tag, Long> {
    Optional<Tag> findByName(String name);
}

// CommentRepository.java
package com.example.blog.repository;

import com.example.blog.entity.Comment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
    // 按文章 ID 查询评论(按创建时间倒序)
    Page<Comment> findByArticleIdOrderByCreatedAtDesc(Long articleId, Pageable pageable);
}

7. 初始化数据 —— 启动时插入示例数据

为了让项目一启动就有数据可以展示,我们创建一个 DataInitializer 组件,在应用启动时插入默认的管理员账号、分类、标签和示例文章。

package com.example.blog;

import com.example.blog.entity.*;
import com.example.blog.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {

    private final UserRepository userRepository;
    private final CategoryRepository categoryRepository;
    private final TagRepository tagRepository;
    private final ArticleRepository articleRepository;

    @Override
    public void run(String... args) {
        // 如果已经有用户,说明数据已初始化,跳过
        if (userRepository.count() > 0) {
            log.info("数据已存在,跳过初始化");
            return;
        }

        log.info("开始初始化示例数据...");

        // 1. 创建管理员用户
        User admin = new User();
        admin.setUsername("admin");
        // 实际项目中密码需要加密,这里先用明文(后面会改成加密)
        admin.setPassword("admin123");
        admin.setEmail("admin@blog.com");
        admin.setAvatar("https://i.pravatar.cc/150?img=1");
        userRepository.save(admin);
        log.info("已创建管理员用户: admin/admin123");

        // 2. 创建分类
        Category javaCat = createCategory("Java", "java");
        Category lifeCat = createCategory("生活随笔", "life");
        Category techCat = createCategory("技术杂谈", "tech");
        List<Category> categories = List.of(javaCat, lifeCat, techCat);
        categoryRepository.saveAll(categories);
        log.info("已创建 {} 个分类", categories.size());

        // 3. 创建标签
        Tag springTag = createTag("Spring");
        Tag jvmTag = createTag("JVM");
        Tag readingTag = createTag("读书笔记");
        Tag thinkingTag = createTag("思考");
        List<Tag> tags = List.of(springTag, jvmTag, readingTag, thinkingTag);
        tagRepository.saveAll(tags);
        log.info("已创建 {} 个标签", tags.size());

        // 4. 创建示例文章
        Article article1 = createArticle(
            "Spring Boot 从入门到精通",
            "spring-boot-tutorial",
            "Spring Boot 是 Java 后端开发中最流行的框架之一。本文带你从零开始,全面掌握 Spring Boot。",
            """
            # Spring Boot 从入门到精通

            Spring Boot 是 Spring 框架的"一键启动"版本,它让 Java Web 开发变得前所未有的简单。

            ## 什么是 Spring Boot?

            Spring Boot 的核心是"约定大于配置"——它为你提供了合理的默认配置,让你只需要关注业务逻辑。

            ## 为什么选择 Spring Boot?

            - 自动配置
            - 内嵌服务器
            - 生产就绪
            - 活跃的社区

            如果你想快速构建一个 Java Web 应用,Spring Boot 是最佳选择。
            """,
            javaCat,
            List.of(springTag, jvmTag),
            admin
        );

        Article article2 = createArticle(
            "JVM 内存模型深度解析",
            "jvm-memory-model",
            "理解 JVM 的内存结构是 Java 程序员进阶的必修课。本文带你深入 JVM 内部。",
            """
            # JVM 内存模型深度解析

            JVM(Java 虚拟机)是 Java 程序运行的基础。理解 JVM 的内存模型,有助于我们写出高性能、无内存泄漏的程序。

            ## 运行时数据区

            JVM 的内存主要分为以下几个区域:

            1. **堆(Heap)**:存放所有对象实例
            2. **方法区(Method Area)**:存放类信息、常量、静态变量
            3. **虚拟机栈(VM Stack)**:存放局部变量、方法调用
            4. **程序计数器(PC Register)**:记录当前线程执行的字节码行号

            掌握这些区域的作用和调优方法,是 Java 进阶的关键一步。
            """,
            techCat,
            List.of(jvmTag, thinkingTag),
            admin
        );

        Article article3 = createArticle(
            "2024 年读书笔记:年度 Top 10",
            "reading-list-2024",
            "2024 年我读了 50 本书,从中精选了 10 本,推荐给喜欢阅读的你。",
            """
            # 2024 年读书笔记:年度 Top 10

            读书是成本最低的自我投资。2024 年我读了 50 本书,下面是我的 Top 10 推荐。

            ## Top 1:《人类群星闪耀时》
            茨威格的经典之作,14 个改变人类命运的历史瞬间,读来令人心潮澎湃。

            ## Top 2:《你当像鸟飞往你的山》
            一个从垃圾堆里爬出来的女孩,最终走向剑桥博士的故事。教育的力量令人震撼。

            ## Top 3:《活着》
            余华的经典,读完后你会对"活着"这两个字有全新的理解。
            """,
            lifeCat,
            List.of(readingTag, thinkingTag),
            admin
        );

        articleRepository.saveAll(List.of(article1, article2, article3));
        log.info("已创建 {} 篇示例文章", 3);

        log.info("示例数据初始化完成!");
        log.info("访问 http://localhost:8080 查看博客");
        log.info("后台管理: http://localhost:8080/admin/login");
    }

    private Category createCategory(String name, String slug) {
        Category category = new Category();
        category.setName(name);
        category.setSlug(slug);
        return category;
    }

    private Tag createTag(String name) {
        Tag tag = new Tag();
        tag.setName(name);
        return tag;
    }

    private Article createArticle(String title, String slug, String summary, String content,
                                  Category category, List<Tag> tags, User author) {
        Article article = new Article();
        article.setTitle(title);
        article.setSlug(slug);
        article.setSummary(summary);
        article.setContent(content);
        article.setCategory(category);
        article.setTags(tags);
        article.setAuthor(author);
        article.setStatus(1);  // 已发布
        article.setPublishedAt(LocalDateTime.now());
        article.setViewCount(0);
        article.setIsTop(false);
        return article;
    }
}

8. 启动并验证

现在,所有准备工作已经完成!让我们启动应用:

  1. 确保 PostgreSQL 服务正在运行,并且数据库 blog 已经创建。
  2. 在 IDEA 中运行 BlogApplicationmain 方法。
  3. 观察控制台日志,如果看到 “示例数据初始化完成” 的日志,说明数据已经成功插入。

此时,数据库中的表已经自动创建(因为 ddl-auto: update),示例数据也已经插入。

你可以用 PostgreSQL 客户端查看数据:

SELECT * FROM t_user;
SELECT * FROM t_category;
SELECT * FROM t_tag;
SELECT * FROM t_article;

如果一切正常,你就有了一个包含三篇示例文章的博客系统后端。只是现在还缺一个前端页面来展示它们。


9. 今天的总结

今天我们完成了个人博客项目的“地基”:

步骤内容
需求分析明确了文章、分类、标签、评论、用户认证五大模块
技术选型Spring Boot 3 + JPA + Thymeleaf + PostgreSQL+ Bootstrap
数据库设计设计了 6 张表,覆盖所有业务需求
项目创建用 Spring Initializr 创建了 Spring Boot 项目
实体类用 JPA 注解创建了 6 个实体类,建立了表之间的关联
Repository创建了 5 个 Repository 接口,定义了常用的数据访问方法
数据初始化在应用启动时自动插入管理员、分类、标签和示例文章

你的项目现在已经可以启动,数据也已经就位。但如果你现在访问 http://localhost:8080,只会看到一个默认的 Whitelabel Error Page——因为我们还没有写 Controller 和前端页面。

下一篇文章,我们会:

  • 创建 Service 层(业务逻辑)
  • 创建 Controller 层(处理 HTTP 请求)
  • 创建 Thymeleaf 模板(前端页面)
  • 集成 Spring Security(登录认证)
  • 完成项目打包与部署

整个博客就会从“数据存在但看不到”变成“一个真正可以访问的网站”。


动手试试

  1. 按照本文步骤,在你的电脑上创建这个博客项目,确保能成功启动,并且数据库中生成了所有表。

  2. 用 PostgreSQL 客户端查询 t_article 表,确认三篇示例文章已成功插入。

  3. 打开 application.yml,把 spring.jpa.show-sql 改为 true,然后重新启动应用,观察控制台输出的 SQL 语句,感受 JPA 是如何自动生成 SQL 的。

  4. (挑战)尝试添加一个 @RestController 来测试数据:创建一个 TestController,在 @GetMapping("/test/articles") 中返回 articleRepository.findAll(),看看能否通过浏览器看到 JSON 格式的文章列表。

我们下一篇见——这个博客很快就会变成一个真正的网站了!😃

📌 获取本系列示例代码请访问 GitCode

Applies basic field behavior in circuit design and demonstrates how it relates to grounding and shielding requirements and techniques in circuit designThis book connects the fundamentals of electromagnetic theory to the problems of interference in all types of electronic design. The text covers power distribution in facilities, mixing of analog and digital circuitry, circuit board layout at high clock rates, and meeting radiation and susceptibility standards. The author examines the grounding and shielding requirements and techniques in circuit design and applies basic physics to circuit behavior. The sixth edition of this book has been updated with new material added throughout the chapters where appropriate. The presentation of the book has also been rearranged in order to reflect the current trends in the field.Grounding and Shielding: Circuits and Interference, Sixth Edition: Includes new material on vias and field control, capacitors as transmission lines, first energy sources, and high speed designs using boards with only two layersDemonstrates how circuit geometry controls performance from dc to gigahertzExamines the use of multi-shielded transformers in clean-power installationsProvides effective techniques for handling noise problems in analog and digital circuitsDiscusses how to use conductor geometry to improve performance, limit radiation, and reduce susceptibility to all types of hardware and systemsGrounding and Shielding: Circuits and Interference, Sixth Edition is an updated guide for circuit design engineers and technicians. It will also serve as a reference for engineers in the semiconductor device industry.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值