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
| Keyword | JPQL snippet | Example |
|---|---|---|
| And | ... AND ... | findByNameAndStatus |
| Or | ... OR ... | findByNameOrEmail |
| Between | BETWEEN x AND y | findByAgeBetween |
| LessThan/GreaterThan | < / > | findByCreatedAtBefore |
| Containing | LIKE %x% | findByTitleContaining |
| StartingWith | LIKE x% | findBySlugStartingWith |
| OrderBy | ORDER BY | findByStatusOrderByCreatedAtDesc |
| Top/First | LIMIT n | findTop5ByStatus |
| Distinct | SELECT DISTINCT | findDistinctByCategory |