Search code examples
hibernatespring-bootmany-to-manycrudput

I have problem with update object in many-to-many (Spring Boot)


I have two classes (User and Achievement) with the relationship many-to-many. When I create and establish the relationship between them, and then I want to update any of the objects using the put method, relationship in "user_achievement_open" table is deleted. Where's my mistake?

The whole is intended to assign a group of tasks to the common title "Achievement". Then the User can choose which Achievement he will do. Different Users can perform the same Achievement. One User can perform several different Achievements at the same time.

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private int userId;

    @Column(name = "username")
    private String username;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "user_data_id")
    private UserData userDataId;

    @OneToMany(mappedBy = "userId")
    private List<Realization> realizationList;

    @ManyToMany
    @JoinTable(
            name = "user_achievement_open",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "achievement_id"))
    private List<Achievement> openAchList;

    @ManyToMany
    @JoinTable(
            name = "user_achievement_finished",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "achievement_id"))
    private List<Achievement> finishedAchList;

    public User() {
    }

    public User (String username) {

        this.username = username;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int id) {
        this.userId = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public UserData getUserDataId() {
        return userDataId;
    }

    public void setUserDataId(UserData userData) {
        this.userDataId = userData;
    }

    public void addData(UserData tempUserData) {

        if (userDataId != null) userDataId = null;
        this.userDataId = tempUserData;
    }

    public List<Realization> getRealizationList() {
        return realizationList;
    }

    public void setRealizationList(List<Realization> realizationList) {
        this.realizationList = realizationList;
    }

    public void addRealization(Realization theRealization) {

        if(realizationList == null) realizationList = new ArrayList<>();
        realizationList.add(theRealization);
        theRealization.setUserId(this);
    }

    public List<Achievement> getOpenAchList() {
        return openAchList;
    }

    public void setOpenAchList(List<Achievement> achievementList) {
        this.openAchList = achievementList;
    }

    public void addOpenAch(Achievement theAchievement) {

        if(openAchList == null) openAchList = new ArrayList<>();
        openAchList.add(theAchievement);
    }

    public void removeOpenAch(Achievement theAchievement) {

        if(openAchList.contains(theAchievement)) openAchList.removeIf(Objects::nonNull);
    }

    public List<Achievement> getFinishedAchList() {
        return finishedAchList;
    }

    public void setFinishedAchList(List<Achievement> finishedAchList) {
        this.finishedAchList = finishedAchList;
    }

    public void addFinishedAch(Achievement theAchievement) {

        if(finishedAchList == null) finishedAchList = new ArrayList<>();
        finishedAchList.add(theAchievement);
    }

    public void removeFinishedAch(Achievement theAchievement) {

        if(finishedAchList.contains(theAchievement)) finishedAchList.remove(theAchievement);
    }
}


@Entity
@Table(name = "achievement")
public class Achievement {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "achievement_id")
    private int achievementId;

    @Column(name = "title")
    private String title;

    @OneToMany(mappedBy = "achievementId",
                cascade = CascadeType.ALL)
    private List<Quest> questList;

    @JsonIgnore
    @ManyToMany
    @JoinTable(
            name = "user_achievement_open",
            joinColumns = @JoinColumn(name = "achievement_id"),
            inverseJoinColumns = @JoinColumn(name = "user_id"))
    private List<User> openUserList;

    @ManyToMany
    @JoinTable(
            name = "user_achievement_finished",
            joinColumns = @JoinColumn(name = "achievement_id"),
            inverseJoinColumns = @JoinColumn(name = "user_id"))
    private List<User> finishedUserList;

    public Achievement() {
    }

    public Achievement(String title) {
        this.title = title;
    }

    public int getAchievementId() {
        return achievementId;
    }

    public void setAchievementId(int achievementId) {
        this.achievementId = achievementId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<Quest> getQuestList() {
        return questList;
    }

    public void setQuestList(List<Quest> questList) {
        this.questList = questList;
    }

    public void addQuest(Quest theQuest) {

        if(questList == null) questList = new ArrayList<>();
        questList.add(theQuest);
        theQuest.setAchievementId(this);
    }

    public List<User> getOpenUserList() {
        return openUserList;
    }

    public void setOpenUserList(List<User> userList) {
        this.openUserList = userList;
    }
}


Solution

  • You should set @JoinTable only on one side, wich is the owner of the relationship. The other side is the inverse end and should only refer to the collection in another class which it is mapped by, which in your case would be @ManyToMany(mappedBy="openUserList") (and the same for the other relationship).

    According to the documentation:

    If the association is bidirectional, one side has to be the owner and one side has to be the inverse end (ie. it will be ignored when updating the relationship values in the association table)

    Hope this helps.