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 user

More Collection Traps

TrapWhat happensFix
@ManyToMany with Listremoving one element deletes all join rows and re-inserts the restuse Set, or map the join table as an entity
JOIN FETCH two collectionsMultipleBagFetchException or a cartesian productfetch one collection per query, or use default_batch_fetch_size
JOIN FETCH + paginationHHH90003004 warning: paging done in memory over all rowspage ids first, then fetch by ids
CascadeType.ALL on @ManyToOnedeleting an item deletes its productcascade only parent → child
FetchType.EAGER anywhereextra joins/queries on every loadeverything LAZY; fetch per use case