-1

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

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

3 Comments

Hello ZoroSenpai, lots of good pointers in your reply. Much appreciated. Is there any particular reason for implementing the hashcode and equal methods in User class differently?
Whenever we work with DataStructure like map or set in which hash code is generated for insertion or retrival it's important to override equals and hashcode for performance reason. Another most important reason is Let's say you don't override equals and hashcode in Users.java When you will insert different users in set of Events.java then it will create issue, therefore you must override your equals and hashcode, if it was List then there is no need. If you are satisfied with my answer then can you please mark it approved!
If you rely on default one then it might cause two different entities to be same and then in your Events: Set<Users> it will contain single entity for a user It will work for smaller case but when your dataset grows then it will create issue

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.