Hibernate. ManyToOne, FetchType.EAGER, N+1 Problem

15 Views Asked by At

I'm currently working on a small project for a gym, managing Events and Trainers. Each Event is required to have exactly one Trainer, while a Trainer can be associated with multiple Events.

To ensure immediate retrieval of Trainer information when accessing an Event, I've configured the FetchType to EAGER. (it's default type for ManyToOne)

Here's how the Event entity is set up:

@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Event {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String title;
    private String description;
    private String registrationMessage;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    @ManyToOne(fetch = FetchType.EAGER)
    **@JoinColumn(nullable = false)
    private Trainer trainer;**

    @ManyToMany
    @JoinTable(
            name = "user_event",
            joinColumns = @JoinColumn(name = "event_id"),
            inverseJoinColumns = @JoinColumn(name = "user_id"))
    private List<User> participants;
}

And here's the Trainer entity:

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Trainer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String lastname;
private String phoneNumber;
private String description;
private String igNickname;
private boolean
@OneToMany
@JoinColumn(name = "TRAINER_ID")
private List\<Event\> events;
}

During testing, I generated mock data comprising 10 Trainers and 20 Events, with each Trainer associated with 2 Events. However, instead of the expected single query, 12 queries were executed. It appears that while the first set of Events successfully retrieves the associated Trainer entity, subsequent Events encounter difficulties locating their corresponding Trainers.

Although I acknowledge that I could mitigate this issue by utilizing FetchType.LAZY and implementing custom fetch queries, I'm keen to understand why the current approach isn't yielding the desired outcome.

Do you have any insights into why this might be occurring and how it can be resolved?

Here are the executed queries for reference:

Hibernate: select e1_0.id,e1_0.description,e1_0.end_date,e1_0.registration_message,e1_0.start_date,e1_0.title,e1_0.trainer_id from event e1_0
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?
Hibernate: select t1_0.id,t1_0.description,t1_0.ig_nickname,t1_0.in_gym,t1_0.lastname,t1_0.name,t1_0.phone_number from trainer t1_0 where t1_0.id=?

Additionally, provided below is the code for the method responsible for retrieving the data, along with the relevant mapper:

...

public List\<EventShortDto\> getTestowe() {
return EventMapper.toShortDtoList(eventRepository.findAll());
}
...

Mapper:


public class EventMapper {
private EventMapper() {}

    public static EventShortDto toShortDto(Event event) {
        return EventShortDto..id(event.getId())
                .title(event.getTitle())
                .startDate(event.getStartDate())
                .trainerId(event.getTrainer().getId())
                .trainerName(event.getTrainer().getName())
                .build();
    }
    public static List<EventShortDto> toShortDtoList(List<Event> eventList) {
        return eventList.stream().map(EventMapper::toShortDto).toList();
    }

...
}

After several tests, I've identified that the issue likely stems from the FetchType being set to EAGER, as changing it to Lazy didn't yield the desired result.

I managed to address this problem by incorporating an EntityGraph, although I believe there might be a more optimal solution available.

In the EventRepository, I made the following adjustments:

    @EntityGraph(attributePaths = "trainer")
    @Query("select e from Event e")
    List<Event> findMyAll();

This modification helped in fetching the necessary data efficiently. However, I'm open to exploring alternative approaches if there are better solutions available.

0

There are 0 best solutions below