Dmitry  Adonin
Dmitry Adonin

Reputation: 1204

How to map one-to-many relation properly in Hibernate?

I have two entities, Event (it's movie in cinema actually) and Ticket.

@Entity
public class Event {

    @Id
    @GeneratedValue
    private Integer id;
    @OneToOne
    private Movie movie;
    @Embedded
    private Auditorium auditorium;
    private LocalDateTime startDateTime;
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "event")
    private List<Ticket> tickets = new ArrayList<>();
// getters/setters, etc.

Event contains list of Ticket's to sell, and every Ticket should point to exactly one Event.

@Entity
public class Ticket {

    @Id
    @GeneratedValue
    private Integer id;
    @ManyToOne(cascade = CascadeType.ALL, targetEntity = Event.class)
    private Event event;
    @OneToOne
    private User user;
    @Embedded
    private Seat seat;
    private TicketState state;
    private Float price;
// getters/setters, etc.

I have EventService with such a method (Auditorium contains only amount of seats and numbers of VIP seats), bookingDao just save Ticket in DB:

@Override
@Transactional
public boolean assignAuditorium(Integer eventId, Auditorium auditorium, LocalDateTime datetime) {
        for (Event event : getAll()) {
            if (auditorium.equals(event.getAuditorium()) && intersectsWithAnotherEvent(event, datetime)) {
                return false;
            }
        }
        Event currentEvent = getById(eventId);
        currentEvent.setAuditorium(auditorium);
        currentEvent.setStartDateTime(datetime);
        List<Ticket> tickets = currentEvent.getTickets();
        Integer seatsNumber = auditorium.getSeatsNumber();
        List<Integer> vipSeats = auditorium.getVipSeats();
        for (int i = 1; i <= seatsNumber; i++) {
            Ticket ticket = new Ticket();
            ticket.setEvent(currentEvent);
            ticket.setState(TicketState.FREE);
            ticket.setSeat(new Seat(i, vipSeats.contains(i) ? SeatStatus.VIP : SeatStatus.STANDARD));
            bookingDao.create(ticket);
            tickets.add(ticket);
        }
        return true;
    }
}

The problem is: after creation of, f.e. 10 Ticket's (according to amount of seats in Auditorium) I have 10 (!!!) identical Event's as a result of eventService.getAll() with list of Ticket's of size 10, each ticket has proper Event (as expected), instead of one Event.

So, how to map these entities properly to have one-event-to-many-tickets? Thank you!

UPDATE Implementation of getAll()

@Override
@Transactional
public List<Event> getAll() {
    return eventDao.getAll();
}

@Override
public List<Event> getAll() {
    return sessionFactory.getCurrentSession().createCriteria(Event.class).list();
}

enter image description here

UPDATE2 I have such a picture right after the end of assignAuditorium method, suppose, then @Transactional ends to work. Than I'm inside this method getAll() shows one Event with list of Ticket's as expected. Maybe it will help.

Upvotes: 1

Views: 97

Answers (1)

JB Nizet
JB Nizet

Reputation: 692201

That is caused by the EAGER fetch type on your one to many association. My first advice would be to leave it lazy: you really don't want to load the 250 tickets every time you want to get some information about an event. I would probably even avoid the OneToMany association.

But, in order to solve this, you need to use distinct. Either by adding a DistinctRootEntity result transformer to your criteria query or, preferrably, by using a JPQL query instead of a criteria query:

select distinct e from Event e

Upvotes: 2

Related Questions