spring-data-jpa

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring 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:
  1. Is this an aggregate root? - Only aggregate roots get repositories
  2. Query complexity? - Simple lookup or complex filtering?
  3. Read vs Write? - Commands (write) or queries (read)?
  4. Performance critical? - Large datasets, pagination, or projections?
思考以下问题:
  1. 这是聚合根吗? - 仅聚合根需要创建仓库
  2. 查询复杂度如何? - 是简单查询还是复杂过滤?
  3. 读操作还是写操作? - 命令(写)还是查询(读)?
  4. 是否对性能有严格要求? - 大数据集、分页还是投影?

Step 2: Choose Pattern

步骤2:选择合适的模式

PatternWhenRead
Simple RepositoryBasic CRUD, 1-2 custom queries-
@Query RepositoryMultiple filters, joins, sorting
references/query-patterns.md
DTO ProjectionRead-only, performance-critical
references/dto-projections.md
Custom RepositoryComplex logic, bulk ops, Criteria API
references/custom-repositories.md
CQRS Query ServiceSeparate read/write, multiple projections
references/cqrs-query-service.md
Decision criteria:
NeedSimple@QueryDTOCustomCQRS
Basic CRUD
Custom Queries
Best Performance✅✅✅✅✅✅
Complex Logic
Read/Write Separation✅✅
模式适用场景参考文档
简单仓库基础CRUD操作、1-2个自定义查询-
@Query仓库多条件过滤、关联查询、排序
references/query-patterns.md
DTO投影只读场景、性能敏感查询
references/dto-projections.md
自定义仓库复杂逻辑、批量操作、Criteria API
references/custom-repositories.md
CQRS查询服务读写分离、多投影场景
references/cqrs-query-service.md
决策标准:
需求简单仓库@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.md
java
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:
assets/query-repository.java
- Complete template with examples
适用于3个以上过滤条件、关联查询或需要提升可读性的场景。参考文档:
references/query-patterns.md
java
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.java
- 包含示例的完整模板

DTO Projections

DTO投影

For read-only, performance-critical queries. Read:
references/dto-projections.md
java
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:
assets/dto-projection.java
- Records, interfaces, native queries
适用于只读、性能敏感的查询场景。参考文档:
references/dto-projections.md
java
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.java
- 包含记录、接口、原生查询的示例

Custom Repository

自定义仓库

For Criteria API, bulk ops. Read:
references/custom-repositories.md
java
// 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:
assets/custom-repository.java
- Complete pattern
适用于Criteria API、批量操作场景。参考文档:
references/custom-repositories.md
java
// 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.java
- 完整的实现模式

CQRS Query Service

CQRS查询服务

For Tomato/DDD architectures. Read:
references/cqrs-query-service.md
java
// 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:
assets/query-service.java
- Full CQRS pattern with JdbcTemplate
适用于Tomato/DDD架构。参考文档:
references/cqrs-query-service.md
java
// 仓库(包私有)- 仅处理写操作
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")
            )
        );
    }
}
资源:
assets/query-service.java
- 结合JdbcTemplate的完整CQRS模式

Step 4: Entity Relationships

步骤4:实体关系

Read:
references/relationships.md
for detailed guidance
Quick 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:
assets/relationship-patterns.java
- All relationship types with examples
参考文档:
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.java
- 包含所有关系类型的示例

Step 5: Performance Optimization

步骤5:性能优化

Read:
references/performance-guide.md
for complete checklist
Critical optimizations:
  1. 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();
  2. Use pagination:
    java
    Pageable pageable = PageRequest.of(0, 20);
    Page<Product> page = repository.findByCategory("Electronics", pageable);
  3. Mark read services as readOnly:
    java
    @Service
    @Transactional(readOnly = true)
    public class ProductQueryService { }
  4. Configure batch size:
    yaml
    spring.jpa.properties.hibernate.jdbc.batch_size: 25
参考文档:
references/performance-guide.md
获取完整检查清单
关键优化点:
  1. 避免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();
  2. 使用分页:
    java
    Pageable pageable = PageRequest.of(0, 20);
    Page<Product> page = repository.findByCategory("Electronics", pageable);
  3. 标记读服务为只读:
    java
    @Service
    @Transactional(readOnly = true)
    public class ProductQueryService { }
  4. 配置批量大小:
    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
    @Transactional(readOnly = true)
    at class level for query services
  • Put
    @Transactional
    at service layer, not repository
  • Override with
    @Transactional
    for write methods in read services
最佳实践:
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'tDoWhy
Repository for every entityOnly for aggregate rootsMaintains boundaries
Use save() blindlyUnderstand persist/mergeAvoids unnecessary SELECT
Long query method namesUse @QueryReadability
findAll() without paginationUse Page<> or StreamMemory issues
Fetch entities for read viewsUse DTO projectionsPerformance
FetchType.EAGERLAZY + JOIN FETCHAvoids N+1
@ManyToManyUse join entityAllows relationship attributes
@Transactional in repositoryPut in service layerProper boundaries
Return entities from controllersReturn DTOs/VMsPrevents lazy issues
禁止做法推荐做法原因
为每个实体创建仓库仅为聚合根创建仓库维护边界清晰
盲目使用save()方法理解persist/merge的区别避免不必要的SELECT查询
使用长查询方法名称使用@Query提升可读性
无分页调用findAll()使用Page<>或Stream避免内存问题
为读视图加载实体使用DTO投影提升性能
使用FetchType.EAGERLAZY + 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.md

3. 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-repository.java
    - @Query examples, pagination, bulk ops
  • dto-projection.java
    - Records, interfaces, native queries
  • custom-repository.java
    - Criteria API, EntityManager
  • query-service.java
    - CQRS with JdbcTemplate
  • relationship-patterns.java
    - All JPA associations
assets/
目录下的所有模板:
  • query-repository.java
    - @Query示例、分页、批量操作
  • dto-projection.java
    - 记录、接口、原生查询
  • custom-repository.java
    - Criteria API、EntityManager
  • query-service.java
    - 结合JdbcTemplate的CQRS模式
  • relationship-patterns.java
    - 所有JPA关联类型示例

References

参考资料