Geoffrey De Vylder
Geoffrey De Vylder

Reputation: 4153

Many to many with custom entity in hibernate - cascade not working properly

In my project I need 2 entities with a bi-directional many to many relationship. However, the join table must have its own extra relations with other tables so I created a new entity for the join table myself.

It should look like this:

Project -- InspectorAssignment -- User

When adding an inspectorassignment to a project and persisting it, the assignment should be persisted as well and the user should have the assignment added as well.

To do this I have a method in a service:

projectService.assignInspectorToProject(inspector, project);

When I however run this method two times two records get created in my table. This should not happen as all of the collections are Sets.

Can someone point me out what I am doing wrong?

All relevant methods / classes:

ProjectService

public void assignInspectorToProject(User user, Project project) {

    if (!user.hasRole("inspector")) {
        throw new RuntimeException("This user is no inspector");
    }

    project.addInspector(user);
    projectDao.save(project);
}

Project

@Entity
public class Project extends AbstractPersistentObject {

    @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "project")
    private Set<InspectorAssignment> inspectorAssignments;  

    public void addInspector(User user) {

        InspectorAssignment assignment = new InspectorAssignment(user, this);
        user.addInspectorAssignment(assignment);

        inspectorAssignments.add(assignment);
    }
}

InspectorAssignment

@Entity
public class InspectorAssignment extends AbstractPersistentObject {

    @ManyToOne
    @NotNull
    private User user;

    @ManyToOne
    @NotNull
    private Project project;

    public InspectorAssignment() {
    }

    public InspectorAssignment(User user, Project project) {
        setUser(user);
        setProject(project);
    }
}

User

@Entity
public class User extends AbstractPersistentObject implements UserDetails {

     @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL})
     private Set<InspectorAssignment> inspectorAssignments;

     public void addInspectorAssignment(InspectorAssignment assignment) {
         inspectorAssignments.add(assignment);
     }
}
}

equals and hashcode method in AbstractPersistenObject (the class all my entities extend)

 @Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    AbstractPersistentObject other = (AbstractPersistentObject) obj;
    if (id == null) {
        if (other.id != null)
            return false;
    } else if (id.equals(other.id))
        return true;
    return false;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

Upvotes: 1

Views: 729

Answers (1)

JB Nizet
JB Nizet

Reputation: 691645

That seems pretty logical to me. You call this method twice. So you create two different instances of InspectorAssignment and save them both. Even if you save them by saving the project, the project has a set of assignments containing two different instances of InspectorAssignment. The Set would not add the second one it it were equal to the first one, but it's not, since you haven't overridden hashCode() and equals().

I would

  • create a unique constraint on userId-projectId in the assignment table to make sure this doesn't happen and throws an exception instead
  • only create a new InspectorAssignment if it doesn't exist yet
  • create the InspectorAssignment in database by saving it explicitely, rather than counting on the cascade from Project to InspectorAssignment
  • override equals() and hashCode() in InspectorAssignment

Upvotes: 2

Related Questions