4. Spring Boot 数据持久化(JPA)

一、什么是 JPA?

JPA(Java Persistence API):Java 持久化 API,是 Java EE 的标准规范。

Hibernate:JPA 的最流行实现,Spring Boot 默认使用 Hibernate。

Spring Data JPA:Spring 对 JPA 的封装,极大简化了数据库操作。


二、为什么使用 Spring Data JPA?

传统 JDBC Spring Data JPA
手动编写 SQL 无需手写 SQL
手动映射 ResultSet 自动映射实体类
需要管理连接 自动管理连接池
代码冗长 代码简洁

示例对比

传统 JDBC(冗长)

java 复制代码
public User findById(Long id) {
    String sql = "SELECT * FROM users WHERE id = ?";
    try (Connection conn = dataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setLong(1, id);
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            return user;
        }
        return null;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

Spring Data JPA(简洁)

java 复制代码
public User findById(Long id) {
    return userRepository.findById(id).orElse(null);
}

三、MySQL 连接步骤

步骤 1:安装 MySQL

Windows

  1. 下载 MySQL:dev.mysql.com/downloads/m...
  2. 安装 MySQL,记住设置的 root 密码

macOS

bash 复制代码
brew install mysql
brew services start mysql

Linux(Ubuntu)

bash 复制代码
sudo apt update
sudo apt install mysql-server
sudo systemctl start mysql

步骤 2:创建数据库

sql 复制代码
-- 登录 MySQL
mysql -u root -p

-- 创建数据库
CREATE DATABASE spring_boot_demo;

-- 查看数据库
SHOW DATABASES;

-- 退出
EXIT;

步骤 3:添加依赖到 pom.xml

xml 复制代码
<dependencies>
    <!-- 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>
</dependencies>

步骤 4:配置数据库连接

创建 application.properties

properties 复制代码
# MySQL 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA 配置
# 自动创建表:create(每次启动都创建)、create-drop(每次启动创建,关闭时删除)
# update(根据实体类更新表结构)、validate(验证表结构,不修改)
# none(不处理)
spring.jpa.hibernate.ddl-auto=update

# 显示 SQL 语句(方便调试)
spring.jpa.show-sql=true

# 格式化 SQL 语句
spring.jpa.properties.hibernate.format_sql=true

# 数据库方言(指定 MySQL)
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

# 服务器配置
server.port=8080

四、Entity(实体类)

User 实体类示例

java 复制代码
package com.example.myapp.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "姓名不能为空")
    @Size(min = 2, max = 50, message = "姓名长度必须在2-50之间")
    @Column(nullable = false, length = 50)
    private String name;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    @Column(nullable = false, unique = true, length = 100)
    private String email;

    public User() {}

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // 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;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

五、JPA 核心注解详解

User 实体类中的关键注解

注解 作用 说明
@Entity 标记为实体类 对应数据库表
@Table(name = "users") 指定表名 默认使用类名,可自定义
@Id 标记主键 必须唯一标识
@GeneratedValue(strategy = GenerationType.IDENTITY) 主键生成策略 自增主键
@Column(nullable = false) 列约束 非空约束
@Column(unique = true) 列约束 唯一约束
@Column(length = 50) 列约束 长度限制

主键生成策略

策略 说明 适用数据库
IDENTITY 数据库自增 MySQL、PostgreSQL
SEQUENCE 数据库序列 PostgreSQL、Oracle
TABLE 使用单独表存储主键 跨数据库兼容
AUTO 自动选择 Hibernate 根据数据库选择

六、Repository 层(数据访问层)

创建 UserRepository 接口

java 复制代码
package com.example.myapp.repository;

import com.example.myapp.model.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> {
    // 继承 JpaRepository 后,自动获得以下方法:
    // - save(S entity):保存或更新
    // - findById(ID id):根据 ID 查询
    // - findAll():查询所有
    // - deleteById(ID id):根据 ID 删除
    // - count():统计数量
    // - existsById(ID id):判断是否存在

    // 自定义查询方法(方法名即查询语句)
    Optional<User> findByEmail(String email);

    boolean existsByEmail(String email);
}

七、JPA 查询方法

方法名查询(Query Method)

Spring Data JPA 支持通过方法名自动生成 SQL,无需手写查询语句。

方法名 生成的 SQL 说明
findByEmail(String email) SELECT * FROM users WHERE email = ? 根据邮箱查询
findByNameContaining(String name) SELECT * FROM users WHERE name LIKE %?% 根据姓名模糊查询
findByEmailAndPassword(String email, String password) SELECT * FROM users WHERE email = ? AND password = ? 根据邮箱和密码查询
countByName(String name) SELECT COUNT(*) FROM users WHERE name = ? 统计数量
deleteById(Long id) DELETE FROM users WHERE id = ? 删除指定记录

自定义查询示例

java 复制代码
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 根据邮箱查询
    Optional<User> findByEmail(String email);

    // 根据姓名模糊查询
    List<User> findByNameContaining(String name);

    // 根据邮箱和密码查询
    Optional<User> findByEmailAndPassword(String email, String password);

    // 统计数量
    long countByName(String name);

    // 判断是否存在
    boolean existsByEmail(String email);

    // 使用 @Query 自定义 SQL
    @Query("SELECT u FROM User u WHERE u.name = ?1 AND u.email = ?2")
    List<User> findByNameAndEmail(String name, String email);

    // 使用原生 SQL
    @Query(value = "SELECT * FROM users WHERE name LIKE %?1%", nativeQuery = true)
    List<User> findByNameLike(String name);
}

八、完整 CRUD 操作示例

1. 创建用户

java 复制代码
// Controller
@PostMapping
public ResponseEntity<User> create(@Valid @RequestBody User user) {
    // 检查邮箱是否已存在
    if (userService.existsByEmail(user.getEmail())) {
        return ResponseEntity.badRequest().build();
    }

    User createdUser = userService.create(user);
    return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}

生成的 SQL:

sql 复制代码
INSERT INTO users (name, email) VALUES (?, ?);

2. 查询所有用户

java 复制代码
@GetMapping
public ResponseEntity<List<User>> findAll() {
    List<User> users = userService.findAll();
    return ResponseEntity.ok(users);
}

生成的 SQL:

sql 复制代码
SELECT * FROM users;

3. 查询指定用户

java 复制代码
@GetMapping("/{id}")
public ResponseEntity<User> findById(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user == null) {
        throw new ResourceNotFoundException("用户不存在,ID: " + id);
    }
    return ResponseEntity.ok(user);
}

生成的 SQL:

sql 复制代码
SELECT * FROM users WHERE id = ?;

4. 更新用户

java 复制代码
@PutMapping("/{id}")
public ResponseEntity<User> update(@PathVariable Long id, @Valid @RequestBody User user) {
    if (!userService.existsById(id)) {
        throw new ResourceNotFoundException("用户不存在,ID: " + id);
    }
    User updatedUser = userService.update(id, user);
    return ResponseEntity.ok(updatedUser);
}

生成的 SQL:

sql 复制代码
UPDATE users SET name = ?, email = ? WHERE id = ?;

5. 删除用户

java 复制代码
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
    if (!userService.existsById(id)) {
        throw new ResourceNotFoundException("用户不存在,ID: " + id);
    }
    userService.delete(id);
    return ResponseEntity.noContent().build();
}

生成的 SQL:

sql 复制代码
DELETE FROM users WHERE id = ?;

九、测试 JPA 操作

使用 curl 测试

bash 复制代码
# 1. 创建用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "张三",
    "email": "zhangsan@example.com"
  }'

# 2. 查询所有用户
curl http://localhost:8080/api/users

# 3. 查询指定用户
curl http://localhost:8080/api/users/1

# 4. 更新用户
curl -X PUT http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "张三修改",
    "email": "zhangsan_new@example.com"
  }'

# 5. 删除用户
curl -X DELETE http://localhost:8080/api/users/1

十、JPA 核心概念总结

概念 说明
Entity(实体) 对应数据库表,用 @Entity 标记
Repository(仓库) 数据访问接口,继承 JpaRepository
EntityManager JPA 核心 API,管理实体生命周期
Persistence Context 持久化上下文,管理实体状态
Transaction(事务) 保证数据一致性,Spring Boot 默认自动开启

十一、JPA 实体状态

状态 说明 示例
Transient(瞬时) 新创建的对象,未与数据库关联 new User()
Managed(托管) 被 EntityManager 管理,对应数据库记录 userRepository.save(user)
Detached(游离) 曾被管理,但已脱离 提交事务后的对象
Removed(删除) 已标记删除,等待事务提交 userRepository.deleteById(id)

十二、常见问题与解决方案

问题 1:表已存在,启动失败

错误信息:

sql 复制代码
Table 'spring_boot_demo.users' already exists

解决方案: 修改 application.properties

properties 复制代码
# 改为 update(更新表结构)或 none(不处理)
spring.jpa.hibernate.ddl-auto=update

问题 2:连接数据库失败

错误信息:

vbscript 复制代码
Could not create connection to database server

解决方案:

  1. 检查 MySQL 服务是否启动
  2. 检查 application.properties 中的连接信息是否正确
  3. 检查防火墙是否阻止了连接

问题 3:主键冲突

错误信息:

rust 复制代码
Duplicate entry '1' for key 'users.PRIMARY'

解决方案:

java 复制代码
// 更新前先检查是否存在
if (!userRepository.existsById(id)) {
    throw new ResourceNotFoundException("用户不存在");
}
user.setId(id);
userRepository.save(user); // 这里会更新,不会插入新记录

十三、最佳实践

1. 使用 Lombok 简化代码

添加依赖:

xml 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

简化后的 User 类:

java 复制代码
@Entity
@Table(name = "users")
@Data // 自动生成 getter/setter/toString
@NoArgsConstructor // 自动生成无参构造
@AllArgsConstructor // 自动生成全参构造
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "姓名不能为空")
    @Size(min = 2, max = 50)
    private String name;

    @NotBlank(message = "邮箱不能为空")
    @Email
    private String email;
}

2. 使用 @Transactional 管理事务

java 复制代码
@Service
public class UserService {
    @Transactional // 自动开启事务
    public void createUserWithOrders(User user, List<Order> orders) {
        userRepository.save(user);
        orderRepository.saveAll(orders);
        // 如果任一操作失败,全部回滚
    }
}

3. 使用 DTO 分离实体和接口

java 复制代码
// DTO:数据传输对象,用于接口层
public class UserDTO {
    private String name;
    private String email;
    // 不包含敏感信息(如密码)
}

// Entity:数据库实体
@Entity
public class User {
    @Id
    private Long id;
    private String name;
    private String email;
    private String password; // 敏感信息
}

4. 使用分页查询

java 复制代码
// Repository
Page<User> findAll(Pageable pageable);

// Service
public Page<User> findAll(int page, int size) {
    Pageable pageable = PageRequest.of(page, size);
    return userRepository.findAll(pageable);
}

// Controller
@GetMapping
public ResponseEntity<Page<User>> findAll(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int size
) {
    Page<User> users = userService.findAll(page, size);
    return ResponseEntity.ok(users);
}

十四、完整项目结构

css 复制代码
my-app/
├── pom.xml
└── src/main/java/com/example/myapp/
    ├── MyApplication.java
    ├── model/
    │   └── User.java
    ├── repository/
    │   └── UserRepository.java
    ├── service/
    │   └── UserService.java
    ├── controller/
    │   ├── HelloController.java
    │   └── UserController.java
    ├── exception/
    │   ├── GlobalExceptionHandler.java
    │   └── ResourceNotFoundException.java
    └── resources/
        └── application.properties

十五、总结

概念 说明
Spring Data JPA Spring 对 JPA 的封装,简化数据库操作
Entity 数据库表映射,用 @Entity 标记
Repository 数据访问接口,继承 JpaRepository
方法名查询 通过方法名自动生成 SQL
@Query 自定义查询语句
@Transactional 事务管理,保证数据一致性
Pageable 分页查询
相关推荐
yashuk2 小时前
springboot与springcloud对应版本
java·spring boot·spring cloud
我叫黑大帅2 小时前
php 如何使用mysqli连接mysql
后端·面试·php
杰杰7982 小时前
一文掌握在Flask使用SQLAlchemy(上)
后端·python·flask
Rabbit_QL2 小时前
[Token实战]Flask JWT 登录接口
后端·python·flask
无风听海2 小时前
LangGraph Thread 数据清理总结
java·开发语言·jvm·langchain·deep agents
荧焰2 小时前
Spring定时任务设计
后端
strayCat232552 小时前
2. Spring Boot 自动配置原理深度解析
java·spring boot·后端
SimonKing2 小时前
每月500 Credits+不限频对话,这款IDEA插件的免费版诚意拉满
java·后端·程序员
我叫黑大帅2 小时前
PHP mysqli 实用开发指南
后端·面试·php