摘要: 本文是个人博客项目的上篇,详细讲解如何使用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)
| 字段名 | 类型 | 说明 |
|---|---|---|
id | BIGINT | 主键,自增 |
username | VARCHAR(50) | 用户名,唯一 |
password | VARCHAR(100) | 密码(存储加密后的值) |
email | VARCHAR(100) | 邮箱 |
avatar | VARCHAR(200) | 头像 URL |
created_at | DATETIME | 创建时间 |
② 分类表(t_category)
| 字段名 | 类型 | 说明 |
|---|---|---|
id | BIGINT | 主键,自增 |
name | VARCHAR(50) | 分类名称,唯一 |
slug | VARCHAR(50) | URL 友好别名(如 “java”) |
created_at | DATETIME | 创建时间 |
③ 标签表(t_tag)
| 字段名 | 类型 | 说明 |
|---|---|---|
id | BIGINT | 主键,自增 |
name | VARCHAR(50) | 标签名称,唯一 |
created_at | DATETIME | 创建时间 |
④ 文章表(t_article)
| 字段名 | 类型 | 说明 |
|---|---|---|
id | BIGINT | 主键,自增 |
title | VARCHAR(200) | 文章标题 |
slug | VARCHAR(200) | URL 友好别名 |
summary | VARCHAR(500) | 文章摘要 |
content | LONGTEXT | 文章正文(Markdown 格式) |
cover_image | VARCHAR(200) | 封面图片 URL |
view_count | INT | 浏览次数 |
category_id | BIGINT | 外键,指向分类表 |
user_id | BIGINT | 外键,指向用户表(作者) |
status | TINYINT | 状态:0-草稿,1-已发布 |
is_top | BOOLEAN | 是否置顶 |
created_at | DATETIME | 创建时间 |
updated_at | DATETIME | 更新时间 |
published_at | DATETIME | 发布时间 |
⑤ 评论表(t_comment)
| 字段名 | 类型 | 说明 |
|---|---|---|
id | BIGINT | 主键,自增 |
article_id | BIGINT | 外键,指向文章表 |
author_name | VARCHAR(50) | 评论者姓名 |
author_email | VARCHAR(100) | 评论者邮箱 |
content | VARCHAR(1000) | 评论内容 |
created_at | DATETIME | 创建时间 |
is_approved | BOOLEAN | 是否审核通过(本项目中默认 true) |
⑥ 文章-标签关联表(t_article_tag)
| 字段名 | 类型 | 说明 |
|---|---|---|
article_id | BIGINT | 外键,指向文章表 |
tag_id | BIGINT | 外键,指向标签表 |
这是一个多对多关系,所以需要一张中间表。
4. 创建 Spring Boot 项目
4.1 在 Spring Initializr 中创建项目
- 访问 https://start.spring.io 或使用 IDEA 的 Spring Initializr。
- 填写项目信息:
- Group:
com.example - Artifact:
blog - Name:
blog - Description:
个人博客系统 - Package name:
com.example.blog - Packaging:
Jar - Java:
21
- Group:
- 添加依赖(在 Dependencies 中搜索并勾选):
- Spring Web:Web 应用基础
- Spring Data JPA:JPA 数据库访问
- Thymeleaf:模板引擎
- PostgreSQL Driver:PostgreSQL 驱动
- Lombok:简化 getter/setter(可选)
- Spring Boot DevTools:热加载(开发时方便)
- Spring Security:用户认证(可选,我们后面会加)
- 点击 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_at 和 updated_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. 启动并验证
现在,所有准备工作已经完成!让我们启动应用:
- 确保 PostgreSQL 服务正在运行,并且数据库
blog已经创建。 - 在 IDEA 中运行
BlogApplication的main方法。 - 观察控制台日志,如果看到 “示例数据初始化完成” 的日志,说明数据已经成功插入。
此时,数据库中的表已经自动创建(因为 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(登录认证)
- 完成项目打包与部署
整个博客就会从“数据存在但看不到”变成“一个真正可以访问的网站”。
动手试试
-
按照本文步骤,在你的电脑上创建这个博客项目,确保能成功启动,并且数据库中生成了所有表。
-
用 PostgreSQL 客户端查询
t_article表,确认三篇示例文章已成功插入。 -
打开
application.yml,把spring.jpa.show-sql改为true,然后重新启动应用,观察控制台输出的 SQL 语句,感受 JPA 是如何自动生成 SQL 的。 -
(挑战)尝试添加一个
@RestController来测试数据:创建一个TestController,在@GetMapping("/test/articles")中返回articleRepository.findAll(),看看能否通过浏览器看到 JSON 格式的文章列表。
我们下一篇见——这个博客很快就会变成一个真正的网站了!😃
📌 获取本系列示例代码请访问 GitCode。
&spm=1001.2101.3001.5002&articleId=163700189&d=1&t=3&u=31a1497853c842a4b9b2c385a3517bcb)
904

被折叠的 条评论
为什么被折叠?



