| name | jpa-patterns |
| description | JPA/Hibernate patterns and common pitfalls (N+1, lazy loading, transactions, queries). Use when user has JPA performance issues, LazyInitializationException, or asks about entity relationships and fetching strategies. |
JPA Patterns Skill
Best practices and common pitfalls for JPA/Hibernate in Spring applications.
When to Use
- User mentions "N+1 problem" / "too many queries"
- LazyInitializationException errors
- Questions about fetch strategies (EAGER vs LAZY)
- Transaction management issues
- Entity relationship design
- Query optimization
Quick Reference: Common Problems
| Problem | Symptom | Solution |
|---|
| N+1 queries | Many SELECT statements | JOIN FETCH, @EntityGraph |
| LazyInitializationException | Error outside transaction | Open Session in View, DTO projection, JOIN FETCH |
| Slow queries | Performance issues | Pagination, projections, indexes |
| Dirty checking overhead | Slow updates | Read-only transactions, DTOs |
| Lost updates | Concurrent modifications | Optimistic locking (@Version) |
N+1 Problem
The #1 JPA performance killer
The Problem
@Entity
public class Author {
@Id private Long id;
private String name;
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Book> books;
}
List<Author> authors = authorRepository.findAll();
for (Author author : authors) {
System.out.println(author.getBooks().size());
}
Solution 1: JOIN FETCH (JPQL)
public interface AuthorRepository extends JpaRepository<Author, Long> {
@Query("SELECT a FROM Author a JOIN FETCH a.books")
List<Author> findAllWithBooks();
}
List<Author> authors = authorRepository.findAllWithBooks();
Solution 2: @EntityGraph
public interface AuthorRepository extends JpaRepository<Author, Long> {
@EntityGraph(attributePaths = {"books"})
List<Author> findAll();
@EntityGraph(value = "Author.withBooks")
List<Author> findAllWithBooks();
}
@Entity
@NamedEntityGraph(
name = "Author.withBooks",
attributeNodes = @NamedAttributeNode("books")
)
public class Author {
}
Solution 3: Batch Fetching
@Entity
public class Author {
@OneToMany(mappedBy = "author")
@BatchSize(size = 25)
private List<Book> books;
}
spring.jpa.properties.hibernate.default_batch_fetch_size=25
Detecting N+1
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
Lazy Loading
FetchType Basics
@Entity
public class Order {
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
}
Best Practice: Default to LAZY
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
}
LazyInitializationException
@Service
public class OrderService {
public Order getOrder(Long id) {
return orderRepository.findById(id).orElseThrow();
}
}
Order order = orderService.getOrder(1L);
order.getItems().size();
Solutions for LazyInitializationException
Solution 1: JOIN FETCH in query
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);
Solution 2: @Transactional on service method
@Service
public class OrderService {
@Transactional(readOnly = true)
public OrderDTO getOrderWithItems(Long id) {
Order order = orderRepository.findById(id).orElseThrow();
int itemCount = order.getItems().size();