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

4 阅读7分钟

一、什么是 JPA?

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

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

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


二、为什么使用 Spring Data JPA?

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

示例对比

传统 JDBC(冗长)

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(简洁)

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

brew install mysql
brew services start mysql

Linux(Ubuntu)

sudo apt update
sudo apt install mysql-server
sudo systemctl start mysql

步骤 2:创建数据库

-- 登录 MySQL
mysql -u root -p

-- 创建数据库
CREATE DATABASE spring_boot_demo;

-- 查看数据库
SHOW DATABASES;

-- 退出
EXIT;

步骤 3:添加依赖到 pom.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

# 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 实体类示例

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 接口

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 = ?删除指定记录

自定义查询示例

@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. 创建用户

// 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:

INSERT INTO users (name, email) VALUES (?, ?);

2. 查询所有用户

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

生成的 SQL:

SELECT * FROM users;

3. 查询指定用户

@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:

SELECT * FROM users WHERE id = ?;

4. 更新用户

@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:

UPDATE users SET name = ?, email = ? WHERE id = ?;

5. 删除用户

@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:

DELETE FROM users WHERE id = ?;

九、测试 JPA 操作

使用 curl 测试

# 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
EntityManagerJPA 核心 API,管理实体生命周期
Persistence Context持久化上下文,管理实体状态
Transaction(事务)保证数据一致性,Spring Boot 默认自动开启

十一、JPA 实体状态

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

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

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

错误信息:

Table 'spring_boot_demo.users' already exists

解决方案: 修改 application.properties

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

问题 2:连接数据库失败

错误信息:

Could not create connection to database server

解决方案:

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

问题 3:主键冲突

错误信息:

Duplicate entry '1' for key 'users.PRIMARY'

解决方案:

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

十三、最佳实践

1. 使用 Lombok 简化代码

添加依赖:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

简化后的 User 类:

@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 管理事务

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

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

// 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. 使用分页查询

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

十四、完整项目结构

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 JPASpring 对 JPA 的封装,简化数据库操作
Entity数据库表映射,用 @Entity 标记
Repository数据访问接口,继承 JpaRepository
方法名查询通过方法名自动生成 SQL
@Query自定义查询语句
@Transactional事务管理,保证数据一致性
Pageable分页查询