Spring Data JPA Reference

Complete Spring Data JPA guide: entity mapping, repository interfaces, JPQL queries, relationships, pagination, and N+1 prevention.

1. @Entity Mapping

import jakarta.persistence.*;
import java.time.Instant;

@Entity
@Table(name = "articles",
       indexes = @Index(columnList = "slug"),
       uniqueConstraints = @UniqueConstraint(columnNames = "slug"))
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(unique = true, nullable = false, length = 300)
    private String slug;

    @Lob
    @Column(nullable = false, columnDefinition = "TEXT")
    private String content;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 20)
    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;

    @UpdateTimestamp
    private Instant updatedAt;
}

2. Repository Interfaces

import org.springframework.data.jpa.repository.*;
import org.springframework.data.domain.*;

public interface ArticleRepository extends JpaRepository<Article, Long> {

    // Derived query methods
    Optional<Article> findBySlug(String slug);
    List<Article> findByAuthor_Id(Long authorId);
    List<Article> findByStatusOrderByCreatedAtDesc(ArticleStatus status);
    boolean existsBySlug(String slug);
    long countByStatus(ArticleStatus status);

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

    // @Query with native SQL
    @Query(value = "SELECT * FROM articles WHERE MATCH(title, content) AGAINST(?1 IN BOOLEAN MODE)",
           nativeQuery = true)
    List<Article> fullTextSearch(String query);

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

3. Pagination & Sorting

import org.springframework.data.domain.*;

@Service
public class ArticleService {

    public Page<ArticleDto> findPublished(int page, int size, String sortBy) {
        Sort sort = Sort.by(Sort.Direction.DESC, sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);

        Page<Article> result = repo.findByStatus(ArticleStatus.PUBLISHED, pageable);

        return result.map(ArticleDto::from);
    }

    // Slice — no total count query (good for infinite scroll)
    public Slice<ArticleDto> findSlice(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return repo.findByStatus(ArticleStatus.PUBLISHED, pageable).map(ArticleDto::from);
    }
}

4. Entity Relationships

// OneToMany + cascade
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Article> articles = new ArrayList<>();

    // Helper method to maintain bidirectional sync
    public void addArticle(Article article) {
        articles.add(article);
        article.setAuthor(this);
    }
}

// OneToOne
@Entity
public class User {
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "profile_id")
    private Profile profile;
}

// Embeddable value objects
@Embeddable
public record Address(String street, String city, String country) {}

@Entity
public class User {
    @Embedded
    private Address address;
}

5. Fetch & N+1 Prevention

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

// Or define named entity graph on entity
@Entity
@NamedEntityGraph(name = "Article.full",
    attributeNodes = {
        @NamedAttributeNode("author"),
        @NamedAttributeNode(value = "tags", subgraph = "tags.none"),
    }
)
public class Article { ... }

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {
    @EntityGraph("Article.full")
    List<Article> findByStatus(ArticleStatus status);
}

6. Query Method Keywords

KeywordJPQL snippetExample
And... AND ...findByNameAndStatus
Or... OR ...findByNameOrEmail
BetweenBETWEEN x AND yfindByAgeBetween
LessThan/GreaterThan< / >findByCreatedAtBefore
ContainingLIKE %x%findByTitleContaining
StartingWithLIKE x%findBySlugStartingWith
OrderByORDER BYfindByStatusOrderByCreatedAtDesc
Top/FirstLIMIT nfindTop5ByStatus
DistinctSELECT DISTINCTfindDistinctByCategory