javaspring-bootmany-to-many

Java Spring Boot Many-To-Many Data Retreival


Given a 'many-to-many' relationship between Event and Users, I'm having trouble retrieving attendees' details when I retrieve an event by its id.

Output: Event(id=1, attendees=[])

Classes are as follows.

Event.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Builder
public class Event {
    @Id
    @GeneratedValue
    private Long id;    
    @ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    @JoinTable(name="event_user",
        joinColumns=@JoinColumn(name="event_id", referencedColumnName="id"),
        inverseJoinColumns=@JoinColumn(name="user_id", referencedColumnName="user_id")
    )
    private Set<User> attendees;
}

User.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Builder
@Table(name = "users")
public class User{
    @Id
    @GeneratedValue
    private Long user_id;
    private String name;
    @ManyToMany(mappedBy="attendees")
    private Set<Event> events;
}

EventRepo.java

public interface EventRepo extends JpaRepository<Event, Long>{
    Event getById(Integer id);
}

Init.Java

@Component
class Init implements CommandLineRunner {   
    final EventRepo eventRepo;
    public Init(EventRepo eventRepo) {
        this.eventRepo = eventRepo;
    }

    @Override
    public void run(String... args) throws Exception {       
        User u = User.builder().name("test").build();
        Set<User> uset = new HashSet<>();
        uset.add(u);

        Event e = Event.builder()
                .attendees(uset)
                .build();
        eventRepo.save(e);
        
        var event = eventRepo.getById(1);
        System.out.println(event);
    }
}

Database View

enter image description here


Solution

  • Here are some little problem with the code, Let us go through the problems and provide the solution.

    Events.java

    @NoArgsConstructor
    @AllArgsConstructor
    @Entity
    @Table(name = "events")
    public class Event {
        @Id
        @GeneratedValue
        private Long id;
        @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
        @JoinTable(name = "event_user",
                joinColumns = @JoinColumn(name = "event_id"),
                inverseJoinColumns = @JoinColumn(name = "user_id")
        )
        private Set<User> attendees = new HashSet<>();
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Event event = (Event) o;
            return Objects.equals(id, event.id);
        }
    
        @Override
        public int hashCode() {
            return getClass().hashCode();
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Set<User> getAttendees() {
            return attendees;
        }
    
        public void setAttendees(Set<User> attendees) {
            this.attendees = attendees;
        }
    
        @Override
        public String toString() {
            return "Event{" +
                    "id=" + id +
                    ", attendees=" + attendees.size() +
                    '}';
        }
    }
    

    Don't use @Data annotation, since it generates toString() method which will make your program stackOverflow due to infinite recursion of toString() from event to user and user to event and so on..., Therefore, generate your own getter and setter along with toString() method.

    Another thing to notice is that equals and hashCode, while working with Set dataStructure in ManyToMany, always try to implement equals and hashCode.

    User.java

    @NoArgsConstructor
    @AllArgsConstructor
    @Entity
    @Table(name = "users")
    public class User {
        @Id
        @GeneratedValue
        private Long id;
        private String name;
        @ManyToMany(mappedBy = "attendees")
        private Set<Event> events = new HashSet<>();
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            User user = (User) o;
            return Objects.equals(name, user.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Set<Event> getEvents() {
            return events;
        }
    
        public void setEvents(Set<Event> events) {
            this.events = events;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", events=" + events.size() +
                    '}';
        }
    }
    

    Please note that, It's equals and hashcode is different from Event.java

    Now another issue is in the interface 'EventRepo'

    EventRepo

    As in JavaDoc it's mentioned that getById() method is depreciated.

    public interface EventRepository extends JpaRepository<Event, Long> {
        @Override
        Optional<Event> findById(Long id);
    }
    

    we need to use findById() to fetch the Event based on Id.

    and atlast the runner code will look something like

        @Override
        public void run(String... args) throws Exception {
            User u = new User();
            u.setName("test-1");
    
            User u2 = new User();
            u2.setName("test-2");
    
            Set<User> uset = new HashSet<>();
            uset.add(u);
            uset.add(u2);
    
            Event e = new Event();
            e.setAttendees(uset);
    
            u.getEvents().add(e);
            u2.getEvents().add(e);
    
            Event save = eventRepository.save(e);
            System.out.println(save);
    
            Optional<Event> event = eventRepository.findById(1L);
            System.out.println(event);
            System.out.println(event.get().getAttendees());
        }
    

    One of the major issue in your code is that you added users in event But event is not added in users which is the major issue.

    u.getEvents().add(e);

    Hope this would help. I executed this code and got the result.

    enter image description here