Spring Data JPA 从入门到实战完整指南
本文档基于 spring-boot-demo 项目,帮助理解 Repository、Entity、Service 三层架构的协作关系。
📚 目录
-
核心概念:一句话理解
-
ORM:对象与数据库的桥梁
-
Entity:数据库表在 Java 中的投影
-
Repository:不写 SQL 的数据库操作
-
Service:业务逻辑的组织者
-
完整调用链:从 Controller 到数据库
-
如何设计 Repository 方法
-
常见问题与调试技巧
1. 核心概念:一句话理解
Spring Data JPA
ini
Spring Data JPA = 我们不用写 SQL,就能操作数据库的工具
对比传统 JDBC 方式:
java
// ❌ 传统 JDBC:要写这么多代码
String sql = "SELECT * FROM t_order WHERE order_no = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, orderNo);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Order order = new Order();
order.setId(rs.getLong("id"));
order.setOrderNo(rs.getString("order_no"));
// ... 还要一个个字段赋值
}
// ✅ Spring Data JPA:就这一行!
Optional<Order> order = orderRepository.findByOrderNo("ORD123");
2. ORM:对象与数据库的桥梁
什么是 ORM?
ini
ORM = 让 Java 对象和数据库表之间可以互相转换的桥梁
映射关系图
sql
┌─────────────────────────────────────────────────────────────────┐
│ Java 对象(内存中) ↔ 数据库表(硬盘上) │
├─────────────────────────────────────────────────────────────────┤
│ Order 类 ↔ t_order 表 │
│ ├── id: Long ↔ id BIGINT │
│ ├── orderNo: String ↔ order_no VARCHAR │
│ ├── amount: BigDecimal ↔ amount DECIMAL │
│ ├── status: OrderStatus ↔ status VARCHAR │
│ ├── createTime: LocalDateTime ↔ create_time │
│ └── userId: Long ↔ user_id BIGINT │
├─────────────────────────────────────────────────────────────────┤
│ Order 对象实例 ↔ 表中的一行记录 │
│ new Order("ORD123", 99.5) ↔ |1|ORD123|99.5|...│
└─────────────────────────────────────────────────────────────────┘
双向转换过程
方向一:Java → 数据库(写入)
java
Order order = new Order();
order.setOrderNo("ORD123");
order.setAmount(new BigDecimal("99.50"));
orderRepository.save(order);
// ORM 自动执行:
// INSERT INTO t_order (order_no, amount) VALUES ('ORD123', 99.50)
方向二:数据库 → Java(读取)
java
Order order = orderRepository.findByOrderNo("ORD123").get();
// ORM 自动执行:
// SELECT * FROM t_order WHERE order_no = 'ORD123'
// 并将结果转换为 Order 对象
String no = order.getOrderNo(); // 直接拿到 "ORD123"
3. Entity:数据库表在 Java 中的投影
项目中的 Order 实体类
java
@Entity // ← 告诉 JPA:这个类对应一张表
@Table(name = "t_order") // ← 指定表名(不写则默认是 order)
public class Order {
@Id // ← 主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // ← 自增策略
private Long id; // → id BIGINT AUTO_INCREMENT PRIMARY KEY
@Column(name = "order_no", unique = true, nullable = false, length = 32)
private String orderNo; // → order_no VARCHAR(32) UNIQUE NOT NULL
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal amount; // → amount DECIMAL(10,2) NOT NULL
@Enumerated(EnumType.STRING) // ← 枚举存为字符串(推荐)
@Column(nullable = false, length = 20)
private OrderStatus status; // → status VARCHAR(20) NOT NULL
@Column(name = "user_id")
private Long userId; // → user_id BIGINT
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime; // → create_time DATETIME
@Column(name = "update_time")
private LocalDateTime updateTime; // → update_time DATETIME
}
注解对照表
| 注解 | 作用 | 对应数据库 |
|---|---|---|
@Entity |
标记这是数据库表对应的类 | 一张表 |
@Table(name = "...") |
指定表名 | 表名 |
@Id |
标记主键 | 主键列 |
@GeneratedValue |
主键生成策略 | AUTO_INCREMENT |
@Column |
指定列属性 | 列名、类型、约束 |
@Enumerated(STRING) |
枚举存储方式 | 存字符串 |
自动生成的数据库表结构
sql
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(32) UNIQUE NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) NOT NULL,
user_id BIGINT,
create_time DATETIME,
update_time DATETIME
);
4. Repository:不写 SQL 的数据库操作
项目中的 OrderRepository 接口
java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// ========== 自定义查询方法 ==========
/**
* 根据订单号查询订单
* 自动生成:SELECT * FROM t_order WHERE order_no = ?
*/
Optional<Order> findByOrderNo(String orderNo);
/**
* 查询指定状态且创建时间早于某时间的订单
* 用于查询超时未支付的订单
* 自动生成:SELECT * FROM t_order WHERE status = ? AND create_time < ?
*/
List<Order> findByStatusAndCreateTimeBefore(OrderStatus status, LocalDateTime createTime);
/**
* 查询指定状态且发货时间早于某时间的订单
* 用于查询需要自动确认收货的订单
* 自动生成:SELECT * FROM t_order WHERE status = ? AND delivery_time < ?
*/
List<Order> findByStatusAndDeliveryTimeBefore(OrderStatus status, LocalDateTime deliveryTime);
/**
* 查询所有订单,按创建时间倒序
* 自动生成:SELECT * FROM t_order ORDER BY create_time DESC
*/
List<Order> findAllByOrderByCreateTimeDesc();
}
方法命名规则解析
Spring Data JPA 通过解析方法名自动生成 SQL:
| 方法名组成部分 | 含义 | 示例 |
|---|---|---|
findBy |
查询操作 | findByOrderNo |
findAllBy |
查询返回列表 | findAllByStatus |
countBy |
统计数量 | countByStatus |
existsBy |
是否存在 | existsByOrderNo |
deleteBy |
删除操作 | deleteByStatus |
条件关键词对照表
| 关键词 | 生成的 SQL 条件 | 示例 |
|---|---|---|
And |
AND |
findByStatusAndCreateTimeBefore |
Or |
OR |
findByStatusOrOrderNo |
Before |
<(时间比较) |
findByCreateTimeBefore |
After |
>(时间比较) |
findByCreateTimeAfter |
Between |
BETWEEN |
findByPriceBetween |
LessThan |
<(数值比较) |
findByAmountLessThan |
GreaterThan |
>(数值比较) |
findByAmountGreaterThan |
Like |
LIKE |
findByOrderNoLike |
Containing |
LIKE %xxx% |
findByOrderNoContaining |
StartingWith |
LIKE xxx% |
findByOrderNoStartingWith |
EndingWith |
LIKE %xxx |
findByOrderNoEndingWith |
In |
IN (...) |
findByStatusIn(List) |
NotIn |
NOT IN (...) |
findByStatusNotIn(List) |
IsNull |
IS NULL |
findByRemarkIsNull |
IsNotNull |
IS NOT NULL |
findByRemarkIsNotNull |
OrderBy |
ORDER BY |
findAllByOrderByCreateTimeDesc |
JpaRepository 自带的方法
继承 JpaRepository<Order, Long> 后,自动拥有以下方法:
java
// ========== 保存/更新 ==========
orderRepository.save(order); // INSERT 或 UPDATE
orderRepository.saveAll(orders); // 批量保存
// ========== 查询单个 ==========
orderRepository.findById(1L); // 根据 ID 查询,返回 Optional
orderRepository.getById(1L); // 根据 ID 查询,不存在则抛异常
// ========== 查询列表 ==========
orderRepository.findAll(); // 查询所有
orderRepository.findAllById(ids); // 根据 ID 列表查询
// ========== 删除 ==========
orderRepository.delete(order); // 删除对象
orderRepository.deleteById(1L); // 根据 ID 删除
orderRepository.deleteAll(); // 删除所有
// ========== 统计 ==========
orderRepository.count(); // 总数
orderRepository.existsById(1L); // 是否存在
5. Service:业务逻辑的组织者
Service 层的作用
css
Service 层 = 业务逻辑的载体
├── 调用 Repository 来读写数据库
├── 编写业务逻辑(状态校验、金额计算、超时判断等)
├── 管理事务(@Transactional)
└── 给 Controller 提供干净的接口
项目中的 OrderService 示例
java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
// ========== 创建订单 ==========
@Transactional
public Order createOrder(Long userId, BigDecimal amount, String remark) {
// 1. 构建订单对象
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setAmount(amount);
order.setStatus(OrderStatus.CREATED);
order.setCreateTime(LocalDateTime.now());
// 2. 调用 Repository 保存到数据库
// 执行:INSERT INTO t_order (...) VALUES (...)
return orderRepository.save(order);
}
// ========== 查询订单 ==========
public Order getByOrderNo(String orderNo) {
// 调用 Repository 的 findByOrderNo 方法
// 执行:SELECT * FROM t_order WHERE order_no = ?
return orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new RuntimeException("订单不存在:" + orderNo));
}
// ========== 支付订单 ==========
@Transactional
public Order payOrder(String orderNo) {
// 1. 查询订单(调用 Repository)
Order order = getByOrderNo(orderNo);
// 2. 业务逻辑:校验状态
if (order.getStatus() != OrderStatus.CREATED) {
throw new RuntimeException("订单状态不正确,无法支付");
}
// 3. 业务逻辑:校验是否超时
if (order.getCreateTime().plusMinutes(30).isBefore(LocalDateTime.now())) {
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order); // 更新状态
throw new RuntimeException("订单已超时,已自动取消");
}
// 4. 调用支付网关(业务逻辑)
boolean paySuccess = mockPayGateway(order);
// 5. 更新订单状态(调用 Repository)
// 执行:UPDATE t_order SET status = 'PAID', pay_time = ? WHERE id = ?
order.setStatus(OrderStatus.PAID);
order.setPayTime(LocalDateTime.now());
return orderRepository.save(order);
}
// ========== 定时任务:取消超时订单 ==========
@Transactional
public int cancelTimeoutOrders(int minutes) {
LocalDateTime deadline = LocalDateTime.now().minusMinutes(minutes);
// 调用 Repository 的自定义查询方法
// 执行:SELECT * FROM t_order WHERE status = 'CREATED' AND create_time < ?
List<Order> timeoutOrders = orderRepository.findByStatusAndCreateTimeBefore(
OrderStatus.CREATED, deadline
);
// 业务逻辑:批量更新状态
for (Order order : timeoutOrders) {
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
}
return timeoutOrders.size();
}
}
Repository 在 Service 中的调用位置
| Repository 方法 | 在 Service 中被调用 | 作用 |
|---|---|---|
save(order) |
createOrder, payOrder, cancelOrder |
插入或更新 |
findByOrderNo(orderNo) |
getByOrderNo(被所有方法间接调用) |
查询单个订单 |
findAllByOrderByCreateTimeDesc() |
getAllOrders |
查询全部订单 |
findByStatusAndCreateTimeBefore(...) |
cancelTimeoutOrders |
查询超时订单 |
findByStatusAndDeliveryTimeBefore(...) |
autoConfirmReceive |
查询待确认订单 |
6. 完整调用链:从 Controller 到数据库
三层架构调用流程图
scss
┌──────────────────────────────────────────────────────────────────┐
│ 用户请求 │
│ POST /api/orders/create │
└──────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────┐
│ Controller 层 │
│ OrderController.createOrder() │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ @PostMapping("/create") │ │
│ │ public ResponseEntity<Order> createOrder(...) { │ │
│ │ Order order = orderService.createOrder(userId, amount);│ │
│ │ return ResponseEntity.ok(order); │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────┘ │
│ 作用:接收 HTTP 请求,调用 Service,返回响应 │
└──────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────┐
│ Service 层 │
│ OrderService.createOrder() │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ @Transactional │ │
│ │ public Order createOrder(...) { │ │
│ │ Order order = new Order(); │ │
│ │ order.setOrderNo(...); │ │
│ │ return orderRepository.save(order); │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────┘ │
│ 作用:业务逻辑、事务管理、调用 Repository │
└──────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────┐
│ Repository 层 │
│ OrderRepository.save() │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ public interface OrderRepository extends JpaRepository { │ │
│ │ // Spring 自动生成实现类 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────┘ │
│ 作用:数据访问接口,Spring 自动生成实现 │
└──────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────┐
│ JPA/Hibernate 层 │
│ ORM 框架执行转换 │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 1. 解析 Entity 类的注解 │ │
│ │ 2. 将 Order 对象转换为 SQL │ │
│ │ 3. INSERT INTO t_order (...) VALUES (...) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ 作用:对象关系映射,生成 SQL │
└──────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────┐
│ 数据库层 │
│ MySQL │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 执行 SQL 语句 │ │
│ │ 将数据写入 t_order 表 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ 作用:数据持久化存储 │
└──────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────┐
│ 返回结果 │
│ 数据库 → Hibernate 转换为 Order 对象 → Service → Controller │
│ → 用户 │
└──────────────────────────────────────────────────────────────────┘
完整代码示例
java
// ========== Controller 层 ==========
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public ResponseEntity<Order> createOrder(@RequestParam Long userId,
@RequestParam BigDecimal amount) {
Order order = orderService.createOrder(userId, amount, "");
return ResponseEntity.ok(order);
}
@GetMapping("/{orderNo}")
public ResponseEntity<Order> getOrder(@PathVariable String orderNo) {
Order order = orderService.getByOrderNo(orderNo);
return ResponseEntity.ok(order);
}
}
// ========== Service 层 ==========
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public Order createOrder(Long userId, BigDecimal amount, String remark) {
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setAmount(amount);
order.setStatus(OrderStatus.CREATED);
return orderRepository.save(order);
}
public Order getByOrderNo(String orderNo) {
return orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new RuntimeException("订单不存在"));
}
}
// ========== Repository 层 ==========
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Optional<Order> findByOrderNo(String orderNo);
}
// ========== Entity 层 ==========
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_no", unique = true)
private String orderNo;
private BigDecimal amount;
private OrderStatus status;
private Long userId;
private LocalDateTime createTime;
}
7. 如何设计 Repository 方法
根据业务需求倒推
| 业务场景 | 需要的方法 | 设计思路 |
|---|---|---|
| 用户根据订单号查询订单 | findByOrderNo(String orderNo) |
单字段查询,返回 Optional |
| 用户查询自己的所有订单 | findByUserIdOrderByCreateTimeDesc(Long userId) |
按用户查询 + 排序 |
| 定时任务查询超时未支付订单 | findByStatusAndCreateTimeBefore(...) |
多条件查询 |
| 管理后台查询某状态的订单 | findByStatus(OrderStatus status) |
单条件查询,返回 List |
| 查询订单是否存在 | existsByOrderNo(String orderNo) |
返回 boolean |
| 统计某状态的订单数量 | countByStatus(OrderStatus status) |
返回 long |
设计步骤
vbnet
第一步:明确业务需求
├── 用户要做什么操作?
├── 需要查询哪些字段?
├── 需要什么条件?
└── 返回单个对象还是列表?
第二步:按照命名规则写方法名
├── findBy + 字段名 + 条件关键词
├── 注意:字段名要和 Entity 类中的属性名一致
└── 时间比较用 Before/After,数值比较用 LessThan/GreaterThan
第三步:确定返回类型
├── 单个对象 → Optional<Order>
├── 列表 → List<Order>
├── 是否存在 → boolean
└── 统计数量 → long
示例:设计一个新方法
需求:查询某个用户的、已支付状态的、金额大于100元的订单
java
// 第一步:确定条件
// - userId = ?
// - status = PAID
// - amount > 100
// 第二步:按规则拼接
List<Order> findByUserIdAndStatusAndAmountGreaterThan(
Long userId,
OrderStatus status,
BigDecimal amount
);
// 第三步:使用
List<Order> orders = orderRepository.findByUserIdAndStatusAndAmountGreaterThan(
1L,
OrderStatus.PAID,
new BigDecimal("100")
);
// 执行:SELECT * FROM t_order WHERE user_id = 1 AND status = 'PAID' AND amount > 100
8. 常见问题与调试技巧
Q1:方法名写错了,启动报错怎么办?
scss
错误示例:findByOrderNumber(String orderNo)
原因:Entity 类中字段名是 orderNo,不是 orderNumber
解决:改为 findByOrderNo(String orderNo)
Q2:如何查看实际执行的 SQL?
在 application.yml 中配置:
yaml
spring:
jpa:
show-sql: true # 打印 SQL
properties:
hibernate:
format_sql: true # 格式化 SQL
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE # 显示参数绑定
Q3:复杂查询用方法名不好表达怎么办?
使用 @Query 自定义 SQL:
java
@Query("SELECT o FROM Order o WHERE o.userId = :userId AND o.amount BETWEEN :min AND :max")
List<Order> findByUserIdAndAmountRange(@Param("userId") Long userId,
@Param("min") BigDecimal min,
@Param("max") BigDecimal max);
@Query(value = "SELECT * FROM t_order WHERE status = ? AND amount > ?", nativeQuery = true)
List<Order> findCustomByNativeQuery(OrderStatus status, BigDecimal amount);
Q4:IDE 中如何快速找到 Repository 的实现?
重要:Repository 接口没有实现类,Spring 在启动时自动生成代理类。
markdown
在 IDEA 中:
1. 按住 Ctrl 点击 orderRepository.save()
2. 会跳转到 JpaRepository 接口定义
3. 实现类是 Spring 运行时生成的,源码中看不到
调试技巧:
1. 在 Service 方法中打断点
2. 查看变量窗口中的 orderRepository 对象
3. 类型是:$Proxy123 或 SimpleJpaRepository
Q5:为什么有时要用 Optional?
java
Optional<Order> findByOrderNo(String orderNo);
// 好处:强制处理"找不到"的情况
Order order = orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new RuntimeException("订单不存在"));
// 或者返回默认值
Order order = orderRepository.findByOrderNo(orderNo)
.orElse(new Order());
// 或者直接判断是否存在
if (orderRepository.findByOrderNo(orderNo).isPresent()) {
// 存在
}
📌 快速记忆口诀
Entity 注解
less
@Entity 标记表
@Table 指定名
@Id 是主键
@GeneratedValue 自增
@Column 定列名
@Enumerated 存枚举
Repository 方法命名
findBy 查询
countBy 统计
existsBy 判断存在
deleteBy 删除
And/Or 连接条件
Before/After 时间比较
LessThan/GreaterThan 数值比较
Containing 模糊匹配
OrderBy 排序
三层架构
Controller 接请求
Service 做业务
Repository 存数据
Entity 定结构
📝 练习建议
- 打开项目,找到
OrderRepository.java - 尝试添加一个新的查询方法(如:按用户ID查询)
- 在
OrderService.java中调用这个新方法 - 配置
show-sql: true,观察生成的 SQL - 写一个单元测试验证功能
最后总结:
Spring Data JPA 是一个"翻译官":
- 把 Java 对象给它
- 它自动翻译成 SQL 语句
- 然后去数据库执行
- 最后把结果再翻译回 Java 对象返回
Repository 接口只需要定义,Spring 会自动实现。 Entity 类用注解告诉 ORM 怎么映射到数据库表。 Service 类串联业务逻辑,调用 Repository 操作数据。