Код-ревью 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 на сущностях не используем.
Что должен найти ревьюер
- Ошибкастрока 2@Data на сущности: equals/hashCode и toString трогают ленивые связи (лишние запросы, LazyInitializationException, рекурсия через обратные ссылки) и строятся на изменяемых полях.
- Производительностьстрока 6@ManyToOne по умолчанию EAGER: findAll() догрузит покупателей отдельными запросами, нужен явный LAZY.
- Производительностьстрока 8EAGER на коллекции items: позиции грузятся всегда, даже когда не нужны, и при списках это N+1.
- Производительностьстрока 21findAll() без пагинации и фильтра выгружает всю таблицу в память.
- Ошибкастрока 25getPayments() у ленивой коллекции вне транзакции даст LazyInitializationException (метод без @Transactional), а с OSIV это N+1.
- Ошибкастрока 32Две List-коллекции в одном join fetch: MultipleBagFetchException при старте или первом вызове, а если сделать Set, то декартово произведение позиций и платежей.
Разбор
Для выгрузки не нужны сущности целиком: правильнее DTO-проекция одним запросом с агрегатами count по позициям и платежам и постраничная выдача. Связи делаем LAZY, Lombok @Data на сущностях не используем.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.