Careeroza
Large Collections
Do Not Map Everything
User.orders looks harmless in development with 5 orders. In production a reseller has 400,000 orders. Calling user.getOrders().add(order) loads all of them, just to add one.
// RISKY: unbounded collection on the "one" side
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders = new ArrayList<>(); // user.getOrders() can be huge
}
// BETTER: keep only the @ManyToOne side and query with paging
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private User user;
}
public interface OrderRepository extends JpaRepository<Order, Long> {
Page<Order> findByUserIdOrderByCreatedAtDesc(Long userId, Pageable page);
}
// Adding an order never touches a collection
orders.save(new Order(userRepository.getReferenceById(userId), cartItems)); // no SELECT on userMore Collection Traps
| Trap | What happens | Fix |
|---|---|---|
@ManyToMany with List | removing one element deletes all join rows and re-inserts the rest | use Set, or map the join table as an entity |
| JOIN FETCH two collections | MultipleBagFetchException or a cartesian product | fetch one collection per query, or use default_batch_fetch_size |
| JOIN FETCH + pagination | HHH90003004 warning: paging done in memory over all rows | page ids first, then fetch by ids |
CascadeType.ALL on @ManyToOne | deleting an item deletes its product | cascade only parent → child |
FetchType.EAGER anywhere | extra joins/queries on every load | everything LAZY; fetch per use case |