系列回顾:
在上一篇中,我们成功搭建了第一个 Spring Boot 应用,并创建了一个简单的 "Hello World" API。但它就像一个没有记忆的大脑,每次重启后一切都将归零。真正的应用需要将数据持久化,以便随时存取和管理。今天,我们将为我们的应用装上这颗至关重要的"心脏"------数据库。
欢迎回来,未来的实战派开发者!
几乎所有的业务系统,无论是电商网站、社交平台还是企业内部系统,其核心都是对数据的增、删、改、查(CRUD)。如何优雅、高效地与数据库交互,是衡量一个后端开发者能力的重要标准。
幸运的是,Spring Boot 让这一切变得前所未有的简单。它完美地集成了两大主流的持久化框架:
- JPA (Java Persistence API): 一种 ORM (Object-Relational Mapping) 规范。它允许我们像操作普通 Java 对象一样操作数据库表,屏蔽了繁琐的 SQL 语句。对于新项目和快速开发,这是首选。
- MyBatis: 一个半自动的 SQL 映射框架。它能让我们自己编写和控制 SQL,同时又免去了 JDBC 的大量模板代码。对于需要精细优化 SQL 或对接复杂遗留数据库的场景,它更具优势。
本文将以 JPA 为主线,带你完整地走完整合数据库、实现 CRUD 的全过程。同时,我们也会提供 MyBatis 的整合方案作为对比和备选。
准备工作:搭建数据库环境
在开始编码前,你需要一个数据库。我们将使用世界上最流行的开源数据库------MySQL。
- 安装 MySQL: 如果你还没有安装,可以参考官方文档或搜索相关教程进行安装。
- 创建数据库: 使用你喜欢的数据库客户端(如 DBeaver, Navicat, SQLyog)连接到 MySQL,并执行以下 SQL 命令来创建一个新的数据库和一张用于测试的用户表。
sql
-- 创建一个名为 'springboot_db' 的数据库
CREATE DATABASE springboot_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 切换到该数据库
USE springboot_db;
-- 创建一张用户表
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
`email` VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- (可选) 插入一条测试数据
INSERT INTO `user` (name, email) VALUES ('Jack', '[email protected]');
好了,我们的数据"仓库"已经建好,现在开始写代码把它连接起来!
主线剧情:使用 Spring Data JPA 实现 CRUD
我们将通过 6 个清晰的步骤,为上一篇的 my-first-app
项目添加完整的数据库操作功能。
第一步:添加依赖 (pom.xml)
打开项目的 pom.xml
文件,在 <dependencies>
标签内,添加以下两个新的依赖:
xml
<!-- Spring Data JPA 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 连接驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
spring-boot-starter-data-jpa
: 这是 JPA 的核心启动器,它包含了操作数据库所需的一切,包括 JPA API、Hibernate 实现、Spring Data Commons 等。mysql-connector-j
: 这是连接 MySQL 数据库所必需的 JDBC 驱动。scope=runtime
表示这个依赖只在运行时需要。
添加完毕后,记得让 IDEA 刷新 Maven 依赖。
第二步:配置数据源 (application.properties)
打开 src/main/resources/application.properties
文件,添加以下数据库连接信息:
properties
# -- Database Settings --
# 数据源 URL
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
# 数据库用户名
spring.datasource.username=root
# 数据库密码 (请替换为你自己的密码)
spring.datasource.password=your_password
# -- JPA/Hibernate Settings --
# 让 Hibernate 根据实体类自动更新表结构 (开发时常用, 生产环境慎用)
spring.jpa.hibernate.ddl-auto=update
# 在控制台打印执行的 SQL 语句,方便调试
spring.jpa.show-sql=true
# 格式化打印的 SQL
spring.jpa.properties.hibernate.format_sql=true
配置解读:
spring.datasource.*
: 这些是标准的数据源配置,告诉 Spring Boot 如何连接到你的springboot_db
数据库。spring.jpa.hibernate.ddl-auto=update
: 这是一个神奇的配置。它会让 Hibernate(JPA 的一种实现)在应用启动时,对比你的实体类和数据库表结构,如果实体类有新增字段,它会自动在表中添加对应的列。create
会每次都删表重建,validate
只校验不修改,none
什么都不做。在开发阶段使用update
非常方便,但在生产环境,请务必使用validate
或none
,并通过数据库迁移工具(如 Flyway/Liquibase)管理表结构变更。spring.jpa.show-sql=true
: 让你能清楚地看到每次操作背后实际执行的 SQL 是什么,是调试的绝佳帮手。
第三步:创建实体类 (Entity)
实体类是 Java 世界里对数据库表的映射。在 com.example.myfirstapp
包下创建一个 entity
包,然后在其中创建 User.java
类:
java
package com.example.myfirstapp.entity;
import jakarta.persistence.*;
@Entity // 告诉 JPA 这是一个实体类,它将映射到数据库中的一张表
@Table(name = "user") // 指定映射的表名。如果省略,则默认为类名(小写)
public class User {
@Id // 声明这是主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略,IDENTITY 表示自增
private Long id;
@Column(name = "name", length = 30) // 映射到表中的 'name' 列
private String name;
@Column(name = "email", length = 50) // 映射到表中的 'email' 列
private String email;
// --- Getter 和 Setter ---
// (为了代码简洁,此处省略。请使用 IDE 自动生成 Getter 和 Setter)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
通过简单的注解,我们就将一个普通的 Java Pojo 变成了能被 JPA 管理的实体。
第四步:创建仓库接口 (Repository)
Repository 是 Spring Data JPA 提供的最神奇的特性。你只需要定义一个接口,无需编写任何实现代码,就能获得一套完整的 CRUD 功能。
在 com.example.myfirstapp
包下创建一个 repository
包,然后在其中创建 UserRepository.java
接口:
java
package com.example.myfirstapp.repository;
import com.example.myfirstapp.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// 这就是全部代码!
}
- 我们让
UserRepository
继承了JpaRepository
。 JpaRepository<User, Long>
两个泛型参数分别代表:这个 Repository 操作的实体类是User
,该实体类的主键类型是Long
。
就这么简单!现在,UserRepository
已经自动拥有了 save()
, findById()
, findAll()
, deleteById()
等一系列方法。Spring Data JPA 会在运行时为我们动态生成实现。
第五步:创建控制器 (Controller) 并实现 API
现在,我们来改造 controller
,让它调用 UserRepository
来操作数据库。创建一个新的 UserController.java
。
java
package com.example.myfirstapp.controller;
import com.example.myfirstapp.entity.User;
import com.example.myfirstapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users") // 给这个 Controller 下的所有 API 添加一个 /users 的前缀
public class UserController {
@Autowired // 自动注入 UserRepository 的实例
private UserRepository userRepository;
// 1. 新增用户
@PostMapping("/add")
public User addUser(@RequestBody User user) {
return userRepository.save(user);
}
// 2. 根据 ID 查询用户
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userRepository.findById(id).orElse(null); // orElse(null) 表示如果找不到就返回 null
}
// 3. 查询所有用户
@GetMapping("/all")
public List<User> getAllUsers() {
return userRepository.findAll();
}
// 4. 根据 ID 删除用户
@DeleteMapping("/delete/{id}")
public String deleteUserById(@PathVariable Long id) {
userRepository.deleteById(id);
return "User deleted successfully!";
}
}
代码解读:
@Autowired
: Spring 的依赖注入注解,它会自动帮我们找到UserRepository
的实例并注入到userController
中。@RequestMapping("/users")
: 定义了该控制器下所有接口的公共路径前缀。@PostMapping("/add")
: 处理 HTTP POST 请求,用于创建资源。@RequestBody
表示请求体中的 JSON 数据将被自动转换成User
对象。@GetMapping("/{id}")
: 处理 HTTP GET 请求,用于查询资源。@PathVariable
表示 URL 路径中的{id}
值将被绑定到方法的参数id
上。@DeleteMapping("/delete/{id}")
: 处理 HTTP DELETE 请求,用于删除资源。
第六步:测试你的 API
重新启动你的 Spring Boot 应用。现在,我们需要一个 API 测试工具(如 Postman 或 Insomnia)来验证我们的接口。
-
新增用户 (POST)
-
URL:
http://localhost:8080/users/add
-
Method:
POST
-
Body (raw, JSON):
json{ "name": "Alice", "email": "[email protected]" }
-
响应: 你会看到返回了包含 id 的完整用户信息。
-
-
查询所有用户 (GET)
- URL:
http://localhost:8080/users/all
- Method:
GET
- 响应: 你会看到一个包含 Jack 和 Alice 的 JSON 数组。
- URL:
-
根据 ID 查询用户 (GET)
- URL:
http://localhost:8080/users/1
(假设 Jack 的 id 是 1) - Method:
GET
- 响应: 你会看到 Jack 的用户信息。
- URL:
-
删除用户 (DELETE)
- URL:
http://localhost:8080/users/delete/1
- Method:
DELETE
- 响应:
User deleted successfully!
- 再次查询所有用户,你会发现 Jack 已经不在了。
- URL:
至此,你已经完全掌握了使用 Spring Data JPA 进行数据库操作的核心流程!
支线剧情:使用 MyBatis 实现 CRUD (快速对比)
如果你更喜欢手写 SQL,或者你的项目有此要求,整合 MyBatis 也同样简单。
-
添加依赖: 将
pom.xml
中的spring-boot-starter-data-jpa
替换为:xml<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> <!-- 使用一个较新版本 --> </dependency>
-
配置
application.properties
:properties# (数据源配置不变) # MyBatis 配置 mybatis.mapper-locations=classpath:mapper/*.xml # 指定 XML 映射文件的位置 mybatis.configuration.map-underscore-to-camel-case=true # 开启下划线到驼峰的自动映射
-
创建 Mapper 接口: Repository 在 MyBatis 中被称为 Mapper。创建
UserMapper.java
接口。javapackage com.example.myfirstapp.mapper; import com.example.myfirstapp.entity.User; import org.apache.ibatis.annotations.*; @Mapper // 告诉 Spring Boot 这是一个 MyBatis 的 Mapper 接口 public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User findById(Long id); @Insert("INSERT INTO user(name, email) VALUES(#{name}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") // 获取自增主键 void insert(User user); // ... 其他如 update, delete, findAll 等 }
你也可以将 SQL 语句写在
resources/mapper/UserMapper.xml
文件中,实现 SQL 与 Java 代码的解耦。 -
在 Controller 中使用: 将
UserRepository
替换为UserMapper
即可。
对比总结:
- JPA: 几乎不用写 SQL,开发速度极快,更面向对象。
- MyBatis: SQL 自由度高,易于优化,学习曲线更平缓。
两者没有绝对的好坏,选择哪个取决于你的团队技术栈和项目需求。
总结与展望
今天,我们为应用安装了"记忆",实现了最核心的数据持久化功能。你已经掌握了:
- 如何配置 Spring Boot 连接到 MySQL 数据库。
- 使用 Spring Data JPA,通过定义 Entity 和 Repository 接口,优雅地实现数据库 CRUD。
- 使用 Postman 等工具测试后端的 RESTful API。
- (了解了)整合 MyBatis 的基本方法。
现在,你的应用已经具备了一个真实业务系统的雏形。但是,当前的 API 还比较"粗糙":
- 如果查询一个不存在的用户,会返回
null
,前端可能看到一个空白页面。 - 如果新增用户时不传
name
,数据库会报错,而前端会收到一个不友好的 500 错误。 - 每个接口的返回格式都不一样,有的是对象,有的是字符串。
如何让我们的 API 变得更专业、更健壮、更统一?
在下一篇文章 《【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验》 中,我们将着手解决这些问题,让你的应用具备"专业范儿"。我们下期见!