spring-data-jpa
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpring Data JPA Implementation
Spring Data JPA 实现方案
Critical Rules
核心规则
NEVER create repositories for every entity. ALWAYS create repositories only for aggregate roots.
NEVER use complex query method names. ALWAYS use @Query for non-trivial queries.
NEVER use save() blindly. ALWAYS understand persist vs merge semantics (see Vlad Mihalcea's guidance).
切勿为每个实体创建仓库。仅为聚合根创建仓库。
切勿使用复杂的查询方法名称。对于非简单查询,请始终使用@Query。
切勿盲目使用save()方法。务必理解persist和merge的语义(参考Vlad Mihalcea的指导)。
Step 1: Identify Repository Needs
步骤1:确定仓库需求
Ask:
- Is this an aggregate root? - Only aggregate roots get repositories
- Query complexity? - Simple lookup or complex filtering?
- Read vs Write? - Commands (write) or queries (read)?
- Performance critical? - Large datasets, pagination, or projections?
思考以下问题:
- 这是聚合根吗? - 仅聚合根需要创建仓库
- 查询复杂度如何? - 是简单查询还是复杂过滤?
- 读操作还是写操作? - 命令(写)还是查询(读)?
- 是否对性能有严格要求? - 大数据集、分页还是投影?
Step 2: Choose Pattern
步骤2:选择合适的模式
| Pattern | When | Read |
|---|---|---|
| Simple Repository | Basic CRUD, 1-2 custom queries | - |
| @Query Repository | Multiple filters, joins, sorting | |
| DTO Projection | Read-only, performance-critical | |
| Custom Repository | Complex logic, bulk ops, Criteria API | |
| CQRS Query Service | Separate read/write, multiple projections | |
Decision criteria:
| Need | Simple | @Query | DTO | Custom | CQRS |
|---|---|---|---|---|---|
| Basic CRUD | ✅ | ✅ | ❌ | ✅ | ✅ |
| Custom Queries | ❌ | ✅ | ✅ | ✅ | ✅ |
| Best Performance | ✅ | ✅ | ✅✅ | ✅✅ | ✅✅ |
| Complex Logic | ❌ | ❌ | ❌ | ✅ | ✅ |
| Read/Write Separation | ❌ | ❌ | ✅ | ✅ | ✅✅ |
| 模式 | 适用场景 | 参考文档 |
|---|---|---|
| 简单仓库 | 基础CRUD操作、1-2个自定义查询 | - |
| @Query仓库 | 多条件过滤、关联查询、排序 | |
| DTO投影 | 只读场景、性能敏感查询 | |
| 自定义仓库 | 复杂逻辑、批量操作、Criteria API | |
| CQRS查询服务 | 读写分离、多投影场景 | |
决策标准:
| 需求 | 简单仓库 | @Query仓库 | DTO投影 | 自定义仓库 | CQRS |
|---|---|---|---|---|---|
| 基础CRUD | ✅ | ✅ | ❌ | ✅ | ✅ |
| 自定义查询 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 最优性能 | ✅ | ✅ | ✅✅ | ✅✅ | ✅✅ |
| 复杂逻辑 | ❌ | ❌ | ❌ | ✅ | ✅ |
| 读写分离 | ❌ | ❌ | ✅ | ✅ | ✅✅ |
Step 3: Implement Repository
步骤3:实现仓库
Simple Repository
简单仓库
For basic lookups (1-2 properties):
java
public interface ProductRepository extends JpaRepository<ProductEntity, Long> {
Optional<ProductEntity> findByCode(String code);
List<ProductEntity> findByStatus(ProductStatus status);
}Asset: Use existing entity and repository patterns
适用于基础查询(1-2个属性):
java
public interface ProductRepository extends JpaRepository<ProductEntity, Long> {
Optional<ProductEntity> findByCode(String code);
List<ProductEntity> findByStatus(ProductStatus status);
}资源: 复用现有实体和仓库模式
@Query Repository
@Query仓库
For 3+ filters, joins, or readability. Read:
references/query-patterns.mdjava
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
@Query("""
SELECT DISTINCT o
FROM OrderEntity o
LEFT JOIN FETCH o.items
WHERE o.userId = :userId
ORDER BY o.createdAt DESC
""")
List<OrderEntity> findUserOrders(@Param("userId") Long userId);
}Asset: - Complete template with examples
assets/query-repository.java适用于3个以上过滤条件、关联查询或需要提升可读性的场景。参考文档:
references/query-patterns.mdjava
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
@Query("""
SELECT DISTINCT o
FROM OrderEntity o
LEFT JOIN FETCH o.items
WHERE o.userId = :userId
ORDER BY o.createdAt DESC
""")
List<OrderEntity> findUserOrders(@Param("userId") Long userId);
}资源: - 包含示例的完整模板
assets/query-repository.javaDTO Projections
DTO投影
For read-only, performance-critical queries. Read:
references/dto-projections.mdjava
public record ProductSummary(Long id, String name, BigDecimal price) {}
@Query("""
SELECT new com.example.ProductSummary(p.id, p.name, p.price)
FROM ProductEntity p
WHERE p.status = 'ACTIVE'
""")
List<ProductSummary> findActiveSummaries();Asset: - Records, interfaces, native queries
assets/dto-projection.java适用于只读、性能敏感的查询场景。参考文档:
references/dto-projections.mdjava
public record ProductSummary(Long id, String name, BigDecimal price) {}
@Query("""
SELECT new com.example.ProductSummary(p.id, p.name, p.price)
FROM ProductEntity p
WHERE p.status = 'ACTIVE'
""")
List<ProductSummary> findActiveSummaries();资源: - 包含记录、接口、原生查询的示例
assets/dto-projection.javaCustom Repository
自定义仓库
For Criteria API, bulk ops. Read:
references/custom-repositories.mdjava
// 1. Custom interface
public interface ProductRepositoryCustom {
List<ProductEntity> findByDynamicCriteria(SearchCriteria criteria);
}
// 2. Implementation (must be named <Repository>Impl)
@Repository
class ProductRepositoryImpl implements ProductRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
// Implementation using Criteria API
}
// 3. Main repository extends both
public interface ProductRepository extends JpaRepository<ProductEntity, Long>,
ProductRepositoryCustom {
Optional<ProductEntity> findBySku(String sku);
}Asset: - Complete pattern
assets/custom-repository.java适用于Criteria API、批量操作场景。参考文档:
references/custom-repositories.mdjava
// 1. 自定义接口
public interface ProductRepositoryCustom {
List<ProductEntity> findByDynamicCriteria(SearchCriteria criteria);
}
// 2. 实现类(必须命名为<Repository>Impl)
@Repository
class ProductRepositoryImpl implements ProductRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
// 使用Criteria API实现逻辑
}
// 3. 主仓库继承两者
public interface ProductRepository extends JpaRepository<ProductEntity, Long>,
ProductRepositoryCustom {
Optional<ProductEntity> findBySku(String sku);
}资源: - 完整的实现模式
assets/custom-repository.javaCQRS Query Service
CQRS查询服务
For Tomato/DDD architectures. Read:
references/cqrs-query-service.mdjava
// Repository (package-private) - writes only
interface ProductRepository extends JpaRepository<ProductEntity, ProductId> {
Optional<ProductEntity> findBySku(ProductSKU sku);
}
// QueryService (public) - reads only
@Service
@Transactional(readOnly = true)
public class ProductQueryService {
private final JdbcTemplate jdbcTemplate;
public List<ProductVM> findAllActive() {
return jdbcTemplate.query("""
SELECT id, name, price FROM products
WHERE status = 'ACTIVE'
""",
(rs, rowNum) -> new ProductVM(
rs.getLong("id"),
rs.getString("name"),
rs.getBigDecimal("price")
)
);
}
}Asset: - Full CQRS pattern with JdbcTemplate
assets/query-service.java适用于Tomato/DDD架构。参考文档:
references/cqrs-query-service.mdjava
// 仓库(包私有)- 仅处理写操作
interface ProductRepository extends JpaRepository<ProductEntity, ProductId> {
Optional<ProductEntity> findBySku(ProductSKU sku);
}
// 查询服务(公开)- 仅处理读操作
@Service
@Transactional(readOnly = true)
public class ProductQueryService {
private final JdbcTemplate jdbcTemplate;
public List<ProductVM> findAllActive() {
return jdbcTemplate.query("""
SELECT id, name, price FROM products
WHERE status = 'ACTIVE'
""",
(rs, rowNum) -> new ProductVM(
rs.getLong("id"),
rs.getString("name"),
rs.getBigDecimal("price")
)
);
}
}资源: - 结合JdbcTemplate的完整CQRS模式
assets/query-service.javaStep 4: Entity Relationships
步骤4:实体关系
Read: for detailed guidance
references/relationships.mdQuick patterns:
java
// ✅ GOOD: @ManyToOne (most common)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
// ✅ ALTERNATIVE: Just use ID (loose coupling)
@Column(name = "product_id", nullable = false)
private Long productId;
// ❌ AVOID: @OneToMany (query from many side instead)
// Instead: List<OrderItem> items = itemRepository.findByOrderId(orderId);
// ❌ NEVER: @ManyToMany (create join entity instead)
@Entity
public class Enrollment {
@ManyToOne private Student student;
@ManyToOne private Course course;
private LocalDate enrolledAt;
}Asset: - All relationship types with examples
assets/relationship-patterns.java参考文档: 获取详细指导
references/relationships.md快速实现模式:
java
// ✅ 推荐:@ManyToOne(最常用)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
// ✅ 替代方案:仅使用ID(松耦合)
@Column(name = "product_id", nullable = false)
private Long productId;
// ❌ 避免:@OneToMany(从多方发起查询替代)
// 替代方案:List<OrderItem> items = itemRepository.findByOrderId(orderId);
// ❌ 禁止:@ManyToMany(使用关联实体替代)
@Entity
public class Enrollment {
@ManyToOne private Student student;
@ManyToOne private Course course;
private LocalDate enrolledAt;
}资源: - 包含所有关系类型的示例
assets/relationship-patterns.javaStep 5: Performance Optimization
步骤5:性能优化
Read: for complete checklist
references/performance-guide.mdCritical optimizations:
-
Prevent N+1 queries:java
// Use JOIN FETCH @Query("SELECT o FROM Order o JOIN FETCH o.customer") List<Order> findWithCustomer(); // Or use DTO projection @Query("SELECT new OrderSummary(o.id, c.name) FROM Order o JOIN o.customer c") List<OrderSummary> findSummaries(); -
Use pagination:java
Pageable pageable = PageRequest.of(0, 20); Page<Product> page = repository.findByCategory("Electronics", pageable); -
Mark read services as readOnly:java
@Service @Transactional(readOnly = true) public class ProductQueryService { } -
Configure batch size:yaml
spring.jpa.properties.hibernate.jdbc.batch_size: 25
参考文档: 获取完整检查清单
references/performance-guide.md关键优化点:
-
避免N+1查询:java
// 使用JOIN FETCH @Query("SELECT o FROM Order o JOIN FETCH o.customer") List<Order> findWithCustomer(); // 或使用DTO投影 @Query("SELECT new OrderSummary(o.id, c.name) FROM Order o JOIN o.customer c") List<OrderSummary> findSummaries(); -
使用分页:java
Pageable pageable = PageRequest.of(0, 20); Page<Product> page = repository.findByCategory("Electronics", pageable); -
标记读服务为只读:java
@Service @Transactional(readOnly = true) public class ProductQueryService { } -
配置批量大小:yaml
spring.jpa.properties.hibernate.jdbc.batch_size: 25
Step 6: Transaction Management
步骤6:事务管理
Best practices:
java
@Service
@Transactional(readOnly = true) // Class-level for read services
public class ProductService {
public List<ProductVM> findAll() {
// Read operations
}
@Transactional // Override for writes
public void createProduct(CreateProductCmd cmd) {
ProductEntity product = ProductEntity.create(cmd);
repository.save(product);
}
}Rules:
- Use at class level for query services
@Transactional(readOnly = true) - Put at service layer, not repository
@Transactional - Override with for write methods in read services
@Transactional
最佳实践:
java
@Service
@Transactional(readOnly = true) // 读服务的类级别配置
public class ProductService {
public List<ProductVM> findAll() {
// 读操作
}
@Transactional // 覆盖配置以处理写操作
public void createProduct(CreateProductCmd cmd) {
ProductEntity product = ProductEntity.create(cmd);
repository.save(product);
}
}规则:
- 对于查询服务,在类级别使用
@Transactional(readOnly = true) - 将放在服务层,而非仓库层
@Transactional - 在读服务中,为写方法单独配置进行覆盖
@Transactional
Step 7: Testing
步骤7:测试
java
@DataJpaTest
@Testcontainers
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired
private ProductRepository repository;
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void shouldFindProductByCode() {
ProductEntity product = createTestProduct("P001");
repository.save(product);
Optional<ProductEntity> found = repository.findByCode("P001");
assertThat(found).isPresent();
}
}java
@DataJpaTest
@Testcontainers
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired
private ProductRepository repository;
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void shouldFindProductByCode() {
ProductEntity product = createTestProduct("P001");
repository.save(product);
Optional<ProductEntity> found = repository.findByCode("P001");
assertThat(found).isPresent();
}
}Anti-Patterns
反模式
| Don't | Do | Why |
|---|---|---|
| Repository for every entity | Only for aggregate roots | Maintains boundaries |
| Use save() blindly | Understand persist/merge | Avoids unnecessary SELECT |
| Long query method names | Use @Query | Readability |
| findAll() without pagination | Use Page<> or Stream | Memory issues |
| Fetch entities for read views | Use DTO projections | Performance |
| FetchType.EAGER | LAZY + JOIN FETCH | Avoids N+1 |
| @ManyToMany | Use join entity | Allows relationship attributes |
| @Transactional in repository | Put in service layer | Proper boundaries |
| Return entities from controllers | Return DTOs/VMs | Prevents lazy issues |
| 禁止做法 | 推荐做法 | 原因 |
|---|---|---|
| 为每个实体创建仓库 | 仅为聚合根创建仓库 | 维护边界清晰 |
| 盲目使用save()方法 | 理解persist/merge的区别 | 避免不必要的SELECT查询 |
| 使用长查询方法名称 | 使用@Query | 提升可读性 |
| 无分页调用findAll() | 使用Page<>或Stream | 避免内存问题 |
| 为读视图加载实体 | 使用DTO投影 | 提升性能 |
| 使用FetchType.EAGER | LAZY + JOIN FETCH | 避免N+1查询 |
| 使用@ManyToMany | 使用关联实体 | 支持关系属性扩展 |
| 在仓库层使用@Transactional | 放在服务层 | 保证边界合理 |
| 从控制器返回实体 | 返回DTO/VM | 避免懒加载问题 |
Common Pitfalls
常见陷阱
1. LazyInitializationException
1. LazyInitializationException
Problem: Accessing lazy associations outside transaction
Solution: Use DTO projection or JOIN FETCH
java
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);问题: 在事务外访问懒加载关联
解决方案: 使用DTO投影或JOIN FETCH
java
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);2. N+1 Queries
2. N+1查询
Problem: Loading associations in loop
Solution: See
references/performance-guide.md问题: 在循环中加载关联对象
解决方案: 参考
references/performance-guide.md3. Cartesian Product
3. 笛卡尔积
Problem: Multiple JOIN FETCH with collections
Solution: Separate queries or DTO projections
问题: 对集合使用多个JOIN FETCH
解决方案: 拆分查询或使用DTO投影
Quick Reference
快速参考
When to Load References
何时参考相关文档
- Multiple filters/joins needed →
references/query-patterns.md - Read-only, performance-critical →
references/dto-projections.md - Dynamic queries, bulk operations →
references/custom-repositories.md - CQRS, read/write separation →
references/cqrs-query-service.md - Entity associations →
references/relationships.md - Slow queries, N+1 issues →
references/performance-guide.md
- 需要多条件过滤/关联查询 →
references/query-patterns.md - 只读、性能敏感场景 →
references/dto-projections.md - 动态查询、批量操作 →
references/custom-repositories.md - CQRS、读写分离 →
references/cqrs-query-service.md - 实体关联配置 →
references/relationships.md - 慢查询、N+1问题 →
references/performance-guide.md
Available Assets
可用资源
All templates in :
assets/- - @Query examples, pagination, bulk ops
query-repository.java - - Records, interfaces, native queries
dto-projection.java - - Criteria API, EntityManager
custom-repository.java - - CQRS with JdbcTemplate
query-service.java - - All JPA associations
relationship-patterns.java
assets/- - @Query示例、分页、批量操作
query-repository.java - - 记录、接口、原生查询
dto-projection.java - - Criteria API、EntityManager
custom-repository.java - - 结合JdbcTemplate的CQRS模式
query-service.java - - 所有JPA关联类型示例
relationship-patterns.java
References
参考资料
Incorporates best practices from:
- Vlad Mihalcea - Best Spring Data JpaRepository
- Vlad Mihalcea - Spring Data JPA DTO Projections
- Vlad Mihalcea - Spring Data Query Methods
- Vlad Mihalcea - Spring Transaction Best Practices
- Vlad Mihalcea - ManyToOne Best Practices
Browse vladmihalcea.com/blog for deep-dive articles.