Код-ревью Middle Hibernate: N+1, lazy, кеш обновлено

Код-ревью: выгрузка заказов

Вопрос

Найдите проблемы производительности и корректности при работе с Hibernate.

@Entity
@Data
public class Order {
    @Id @GeneratedValue
    private Long id;
    @ManyToOne
    private Customer customer;
    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
    private List<Item> items = new ArrayList<>();
    @OneToMany(mappedBy = "order")
    private List<Payment> payments = new ArrayList<>();
}

@Service
public class ExportService {
    private final OrderRepository repo;

    public ExportService(OrderRepository repo) { this.repo = repo; }

    public List<OrderDto> export() {
        List<Order> orders = repo.findAll();
        List<OrderDto> result = new ArrayList<>();
        for (Order o : orders) {
            result.add(new OrderDto(o.getId(), o.getCustomer().getName(),
                o.getItems().size(), o.getPayments().size()));
        }
        return result;
    }
}

public interface OrderRepository extends JpaRepository<Order, Long> {
    @Query("select o from Order o join fetch o.items join fetch o.payments")
    List<Order> loadAll();
}

Коротко

Для выгрузки не нужны сущности целиком: правильнее DTO-проекция одним запросом с агрегатами count по позициям и платежам и постраничная выдача. Связи делаем LAZY, Lombok @Data на сущностях не используем.

Что должен найти ревьюер

  1. Ошибкастрока 2
    @Data на сущности: equals/hashCode и toString трогают ленивые связи (лишние запросы, LazyInitializationException, рекурсия через обратные ссылки) и строятся на изменяемых полях.
  2. Производительностьстрока 6
    @ManyToOne по умолчанию EAGER: findAll() догрузит покупателей отдельными запросами, нужен явный LAZY.
  3. Производительностьстрока 8
    EAGER на коллекции items: позиции грузятся всегда, даже когда не нужны, и при списках это N+1.
  4. Производительностьстрока 21
    findAll() без пагинации и фильтра выгружает всю таблицу в память.
  5. Ошибкастрока 25
    getPayments() у ленивой коллекции вне транзакции даст LazyInitializationException (метод без @Transactional), а с OSIV это N+1.
  6. Ошибкастрока 32
    Две List-коллекции в одном join fetch: MultipleBagFetchException при старте или первом вызове, а если сделать Set, то декартово произведение позиций и платежей.

Разбор

Для выгрузки не нужны сущности целиком: правильнее DTO-проекция одним запросом с агрегатами count по позициям и платежам и постраничная выдача. Связи делаем LAZY, Lombok @Data на сущностях не используем.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.