Spring Boot 整合 Spring Data JPA 入门:只需注解,告别 SQL
-
- [一、 前言:从 SQL 革命到 ORM](#一、 前言:从 SQL 革命到 ORM)
- [二、 核心概念:ORM 与 JPA 规范](#二、 核心概念:ORM 与 JPA 规范)
-
- [2.1 什么是 ORM?](#2.1 什么是 ORM?)
- [2.2 JPA 与 Hibernate 的关系](#2.2 JPA 与 Hibernate 的关系)
- [三、 Spring Boot 整合实战:从零构建](#三、 Spring Boot 整合实战:从零构建)
-
- [3.1 添加依赖](#3.1 添加依赖)
- [3.2 配置文件](#3.2 配置文件)
- [3.3 创建实体类](#3.3 创建实体类)
- [3.4 创建 Repository 接口](#3.4 创建 Repository 接口)
- [3.5 编写测试类](#3.5 编写测试类)
- [四、 核心魔法:方法名即查询](#四、 核心魔法:方法名即查询)
-
- [4.1 语法规则](#4.1 语法规则)
- [4.2 实战案例](#4.2 实战案例)
- [五、 进阶:JPQL 与 关联查询](#五、 进阶:JPQL 与 关联查询)
-
- [5.1 使用 @Query 注解](#5.1 使用 @Query 注解)
- [5.2 关联映射 (一对多/多对一)](#5.2 关联映射 (一对多/多对一))
- [六、 高级动态查询:Specification](#六、 高级动态查询:Specification)
- [七、 优缺点分析与总结](#七、 优缺点分析与总结)
-
- [7.1 优点](#7.1 优点)
- [7.2 缺点](#7.2 缺点)
- [7.3 总结](#7.3 总结)
Spring Boot 整合 Spring Data JPA
核心理念: ORM
核心优势
整合实战步骤
高级功能与机制
最佳实践与总结
Object Relational Mapping
以面向对象思维操作数据库
自动生成 SQL
极简 CRUD
方法名即查询
无需编写 XML/SQL
环境搭建与依赖
实体类注解定义
Repository 接口
测试与验证
JPQL 与 @Query
关联映射
Specification 动态查询
一、 前言:从 SQL 革命到 ORM
在传统的 Java EE 开发中,我们习惯于使用 JDBC 或 MyBatis 这样的半自动化框架。我们需要手写大量的 SQL 语句,并在 Java 对象和数据库表之间手动进行数据映射。这种方式灵活,但枯燥且容易出错。
Spring Data JPA 是 Spring 基于 Hibernate 框架提供的一套 JPA (Java Persistence API) 规范的实现。它真正的威力在于:让你完全脱离 SQL 语句,通过 Java 对象的思维来操作数据库。
所谓的"告别 SQL",并非指 SQL 消失了,而是 Spring Data JPA 框架在底层自动为你生成了它。
二、 核心概念:ORM 与 JPA 规范
2.1 什么是 ORM?
ORM (Object-Relational Mapping),即对象-关系映射。它的核心思想是:将数据库中的表映射为 Java 中的类,将表的字段映射为类的属性,将表的记录映射为类的实例对象。
2.2 JPA 与 Hibernate 的关系
在面试和开发中,必须理清两者的关系:
- JPA :是 Sun 公司(现 Oracle)提出的 Java 持久化规范。它只是一套接口和注解标准,本身不干活。
- Hibernate :是 JPA 规范的一种具体实现(也是最流行的一种)。它负责真正执行 SQL,处理缓存。
- Spring Data JPA :是 Spring 对 JPA 的进一步封装,它基于 Hibernate,但提供了更高级的 Repository 接口,极大地简化了数据访问层代码。
架构层次图:
开发者代码
Spring Data JPA
Repository接口
JPA 规范接口
Hibernate 实现
JDBC Driver
MySQL/Oracle Database
三、 Spring Boot 整合实战:从零构建
接下来,我们将一步步在 Spring Boot 中整合 Spring Data JPA,实现对用户表的操作。
3.1 添加依赖
在 pom.xml 中引入 spring-boot-starter-data-jpa 和数据库驱动(以 MySQL 为例)。
xml
<dependencies>
<!-- Web 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 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>
<!-- Lombok (可选,用于简化 getter/setter) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3.2 配置文件
在 application.yml 中配置数据源和 JPA 的核心参数。
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/jpa_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: password
jpa:
hibernate:
# 自动更新表结构 (开发环境常用)
# validate: 验证结构,不更新
# update: 自动更新表结构,保留数据
# create: 启动时删除表,重新创建
# create-drop: 启动创建,关闭删除
ddl-auto: update
# 在控制台打印生成的 SQL 语句(便于调试)
show-sql: true
# 格式化输出的 SQL
properties:
hibernate:
format_sql: true
3.3 创建实体类
使用 JPA 注解将 Java 类映射为数据库表。
java
package com.example.jpa.entity;
import jakarta.persistence.*; // 注意:Spring Boot 3.x 使用 jakarta 包
import lombok.Data;
@Data
@Entity // 标记这是一个 JPA 实体类,对应数据库表
@Table(name = "t_user") // 指定表名,默认表名为类名的驼峰转下划线
public class User {
@Id // 标记为主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增策略
private Long id;
@Column(name = "user_name", length = 50, nullable = false) // 映射列名
private String name;
private String email;
private Integer age;
}
注解说明:
@Entity:告诉 Hibernate 这个类需要映射到数据库。@Table:指定表名,如果不写,默认以类名为表名。@Id:必填,声明主键。@GeneratedValue:定义主键生成策略,IDENTITY对应数据库自增。@Column:定义列属性,如长度、是否为空等。
3.4 创建 Repository 接口
这是最神奇的一步。你甚至不需要写实现类,只需要创建一个接口并继承 JpaRepository。
java
package com.example.jpa.repository;
import com.example.jpa.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* JpaRepository<实体类型, 主键类型>
* Spring Data JPA 会自动帮你生成实现类
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 这里我们甚至不需要写任何代码,就已经拥有了基本的 CRUD 功能!
}
3.5 编写测试类
java
package com.example.jpa;
import com.example.jpa.entity.User;
import com.example.jpa.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Optional;
@SpringBootTest
class JpaDemoApplicationTests {
@Autowired
private UserRepository userRepository;
// 1. 新增
@Test
public void testSave() {
User user = new User();
user.setName("张三");
user.setEmail("zhangsan@example.com");
user.setAge(25);
User savedUser = userRepository.save(user);
System.out.println("保存成功,ID为: " + savedUser.getId());
}
// 2. 查询全部
@Test
public void testFindAll() {
List<User> users = userRepository.findAll();
users.forEach(System.out::println);
}
// 3. 根据 ID 查询
@Test
public void testFindById() {
Optional<User> user = userRepository.findById(1L);
if (user.isPresent()) {
System.out.println(user.get());
}
}
// 4. 修改 (先查再改)
@Test
public void testUpdate() {
Optional<User> optional = userRepository.findById(1L);
if (optional.isPresent()) {
User user = optional.get();
user.setAge(30);
userRepository.save(user); // save 方法在 ID 存在时是更新,不存在时是插入
}
}
// 5. 删除
@Test
public void testDelete() {
userRepository.deleteById(1L);
}
}
四、 核心魔法:方法名即查询
Spring Data JPA 最令人着迷的功能之一。你不需要写 SQL,也不写注解,只要在 Repository 接口中按照规范定义方法名,框架就能解析出你的意图并生成 SQL。
4.1 语法规则
find + By + 属性名 + 查询条件 + And/Or + 属性名 + ...
流程图:
渲染错误: Mermaid 渲染失败: Parse error on line 4: ...--> D[Subject: find (执行查询)] C --> E[ -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
4.2 实战案例
修改 UserRepository 接口:
java
// 1. 根据名字查询
User findByName(String name);
// 2. 根据名字和年龄查询
User findByEmailAndPassword(String email, String password);
// 3. 查询年龄大于指定值的数据 (Between, LessThan, GreaterThan)
List<User> findByAgeGreaterThan(Integer age);
// 4. 模糊查询 (Like)
List<User> findByNameContaining(String keyword);
// 5. 排序 (OrderBy...Asc/Desc)
List<User> findByAgeOrderByNameDesc(Integer age);
// 生成的 SQL 示例:
// SELECT * FROM t_user WHERE age > ? ORDER BY name DESC
五、 进阶:JPQL 与 关联查询
当方法名查询无法满足复杂的业务逻辑(如多表关联、复杂的聚合函数)时,我们需要编写特定的查询语句。JPA 提供了 JPQL (Java Persistence Query Language),它类似 SQL,但操作的是对象和属性,而不是表和列。
5.1 使用 @Query 注解
java
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Long> {
// 使用 JPQL 查询 (User 是类名,不是表名)
@Query("SELECT u FROM User u WHERE u.name = ?1")
User findUserByName(String name);
// 使用原生 SQL
@Query(value = "SELECT * FROM t_user WHERE email = ?1", nativeQuery = true)
User findUserByEmailNative(String email);
// 命名参数
@Query("SELECT u FROM User u WHERE u.name = :name AND u.age = :age")
User findUserByNameAndAge(@Param("name") String name, @Param("age") Integer age);
// 更新操作 (需要配合 @Modifying)
@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
void updateUserEmail(@Param("id") Long id, @Param("email") String email);
}
5.2 关联映射 (一对多/多对一)
在业务中,用户通常会有订单。我们用 OneToMany 来模拟。
java
@Entity
@Data
public class User {
// ...
// FetchType.LAZY 表示懒加载(不立即查订单)
// mappedBy = "user" 表示关系由 Order 类中的 user 字段维护
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Order> orders;
}
@Entity
@Data
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNo;
// 多对一:多个订单属于一个用户
@ManyToOne
@JoinColumn(name = "user_id") // 外键列名
private User user;
}
JPA 关联数据流向图:
1
映射关系
N
对应
User 对象
持有 List
外键 user_id
Order 对象
持有 User 对象
六、 高级动态查询:Specification
虽然方法名查询很方便,但查询条件不确定时(例如搜索框可能只填名字,也可能只填年龄),方法名就会爆炸。此时可以使用 JpaSpecificationExecutor。
1. 继承接口:
java
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
// ...
}
2. 构建动态查询:
java
// Specification 是一个函数式接口,Root 代表查询的根对象
Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.hasText(name)) {
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
if (age != null) {
predicates.add(cb.equal(root.get("age"), age));
}
// AND 连接所有条件
return cb.and(predicates.toArray(new Predicate[0]));
};
List<User> users = userRepository.findAll(spec);
七、 优缺点分析与总结
7.1 优点
- 极大减少代码量:简单的 CRUD 甚至不需要写一行代码,Repository 接口自动实现。
- 面向对象:完全屏蔽了 SQL,开发者只需关注 Java 对象。
- 数据库无关性:基于 JPA 规范,切换数据库(如从 MySQL 切换到 Oracle)只需改配置文件,无需改代码。
- 可移植性:规范的接口设计,便于维护和扩展。
7.2 缺点
- 性能损耗:ORM 框架在运行时生成 SQL,存在反射和代理的开销。
- SQL 调优困难:虽然可以通过日志看 SQL,但无法像 MyBatis 那样精确定位和优化复杂的 SQL 逻辑。
- 学习曲线:虽然上手容易,但精通级联、缓存、抓取策略等高级特性需要较深的理论基础。
7.3 总结
Spring Boot 整合 Spring Data JPA 是构建中小型项目、快速原型开发、领域驱动设计(DDD) 的最佳选择。它让开发者能够以对象模型为核心,通过简单的注解和方法定义即可完成数据持久化,真正实现了"只关注业务逻辑,不再纠结 SQL"。
技术选型建议:
- 项目需要极致灵活 ,SQL 语句极其复杂,且 DBA 需深度参与调优 -> MyBatis / MyBatis-Plus。
- 项目业务逻辑相对标准 ,追求开发效率,且希望保持代码的对象化整洁 -> Spring Data JPA 。
希望这篇图文教程能帮助你顺利入门 Spring Data JPA!