Spring Data JPA 参考

Spring Data JPA 完整指南:实体映射、Repository 接口、JPQL 查询、关系、分页和 N+1 防御。

1. @Entity 映射

@Entity
@Table(name = "articles", indexes = @Index(columnList = "slug"))
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 300)
    private String title;

    @Enumerated(EnumType.STRING)
    private ArticleStatus status = ArticleStatus.DRAFT;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id", nullable = false)
    private User author;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "article_tags",
               joinColumns = @JoinColumn(name = "article_id"),
               inverseJoinColumns = @JoinColumn(name = "tag_id"))
    private Set<Tag> tags = new HashSet<>();

    @CreationTimestamp
    private Instant createdAt;
}

2. Repository 接口

public interface ArticleRepository extends JpaRepository<Article, Long> {
    Optional<Article> findBySlug(String slug);
    List<Article> findByStatusOrderByCreatedAtDesc(ArticleStatus status);
    boolean existsBySlug(String slug);

    @Query("SELECT a FROM Article a WHERE a.status = 'PUBLISHED' ORDER BY a.createdAt DESC")
    List<Article> findRecentPublished(Pageable pageable);

    @Modifying
    @Query("UPDATE Article a SET a.viewCount = a.viewCount + 1 WHERE a.id = :id")
    void incrementViewCount(@Param("id") Long id);
}

3. 分页与排序

public Page<ArticleDto> findPublished(int page, int size, String sortBy) {
    Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, sortBy));
    return repo.findByStatus(ArticleStatus.PUBLISHED, pageable).map(ArticleDto::from);
}

4. 防止 N+1 问题

// 使用 @EntityGraph 避免 N+1
@EntityGraph(attributePaths = {"author", "tags"})
@Query("SELECT a FROM Article a WHERE a.status = :status")
List<Article> findWithAuthorAndTags(@Param("status") ArticleStatus status);