Spring Data JPA 实战建议
Spring Data JPA 写 CRUD 很快,但实战中不能把 Entity、接口 DTO、事务、关联关系和复杂查询混在一起。否则项目早期很省事,后期会出现 SQL 不可控、懒加载异常、循环引用和性能问题。
本页偏项目边界和实践规则。Repository 代理、查询解析、DTO 投影、分页 count 和慢查询排查的完整过程见 Spring Data JPA 核心全过程原理。
推荐分层
mermaid
flowchart TD
A["Controller"] --> B["Request DTO"]
B --> C["Service 业务逻辑"]
C --> D["Repository 数据访问"]
D --> E["Entity 持久化模型"]
C --> F["Response DTO"]
F --> A职责:
| 层 | 做什么 |
|---|---|
| Controller | 接收请求和返回响应 |
| DTO | 表达接口入参和出参 |
| Service | 业务规则、事务边界 |
| Repository | 数据访问 |
| Entity | 数据库持久化映射 |
不要直接返回 Entity
直接返回 Entity 的问题:
- 可能暴露数据库字段。
- 可能触发懒加载异常。
- 双向关联可能导致 JSON 循环引用。
- 接口结构和数据库结构强绑定。
推荐转换 DTO:
java
public record UserDetail(Long id, String username) {
public static UserDetail from(User user) {
return new UserDetail(user.getId(), user.getUsername());
}
}关联关系要克制
JPA 支持一对多、多对一、多对多,但不要为了“对象模型好看”滥用关联。
建议:
- 多对一相对常用,一对多谨慎。
- 多对多通常拆成中间实体。
- 列表查询避免加载大集合。
- 复杂列表优先 DTO 查询。
- 关键接口必须看最终 SQL。
事务建议
java
@Transactional
public void createOrder(CreateOrderRequest request) {
// 读取、校验、修改、保存都在一个业务事务内
}事务边界建议:
- 写操作放 Service。
- 不要把事务放 Controller。
- 事务内不要做慢远程调用。
- 只读查询可以标注
readOnly = true。 - 批量任务分批提交,避免长事务。
查询建议
| 场景 | 推荐方式 |
|---|---|
| 简单唯一查询 | 方法名查询 |
| 固定复杂查询 | @Query JPQL |
| 数据库特性 | 原生 SQL |
| 动态筛选 | Specification |
| 列表展示 | DTO 投影 |
| 报表统计 | 评估 MyBatis、原生 SQL 或数仓 |
SQL 日志
开发和测试环境建议打开 SQL 日志,观察 JPA 生成的真实 SQL。
yaml
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true生产环境不要无节制打印完整 SQL 和参数,避免日志量和敏感信息问题。
测试建议
| 测试 | 关注点 |
|---|---|
| Service 单元测试 | 业务规则 |
| Repository 集成测试 | 查询是否正确 |
| SQL 性能测试 | 慢查询、索引、分页 |
| 事务测试 | 回滚、传播行为 |
Repository 查询尤其要覆盖空条件、边界条件和分页排序。
常见坑
| 问题 | 后果 | 建议 |
|---|---|---|
| Entity 返回接口 | 字段暴露、懒加载、循环引用 | 转 DTO |
| 关联映射过多 | SQL 不可控 | 只建必要关联 |
| 不看 SQL | 性能问题隐藏 | 开启日志和执行计划 |
| 大量方法名查询 | 维护困难 | 复杂查询换方案 |
| 事务范围过大 | 锁等待和连接占用 | 缩小事务 |
小结
Spring Data JPA 实战的关键是边界清晰:Entity 管持久化,DTO 管接口,Service 管事务和业务,Repository 管数据访问。JPA 能提高开发效率,但必须持续关注最终 SQL、关联加载和事务边界。
实战流程原理
mermaid
flowchart TD
A["Controller 接收请求"] --> B["Service 校验并开启事务"]
B --> C["Repository 访问数据库"]
C --> D["Entity 托管和脏检查"]
D --> E["转换 DTO"]
E --> F["返回接口"]为什么要分层:Controller 不应该直接操作 Entity,Repository 不应该写业务决策,Service 负责事务和业务规则。分层清楚后,JPA 的自动持久化能力才不会变成隐式副作用。
继续深入:Spring Data JPA 核心全过程原理。
