-1

The Context

I'm currently exploring JPA and am having trouble saving a ManyToMany relationship. I get the data to be saved as JSON, as in the following example:

{
"oid":"ghi",
"id":9,
"name":"Test 3",
"permissions":[],
"roles":[{"id":2,"name":"MyRole 2","permissions":["PERM1","PERM2"]}]
}

The roles should be reused because they already exist in the database.

I can create new roles and edit existing ones. Creating and editing users without roles also works. However, when I try to create a new user with a role, I get the following error message:

Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: ***.entity.Role

If I want to assign roles to an existing user without roles, I get the following message:

Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.id" is null at ***.entity.Role.hashCode(Role.java:74)

And if I want to remove or edit the roles from an existing user, I get the following message:

Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement Caused by: org.postgresql.util.PSQLException: FEHLER: doppelter Schlüsselwert verletzt Unique-Constraint »user_oid_key«

My Questions

I notice that I have no reference to the user in the role. Does this always have to be present with "mappedBy", even if I don't need it?

My expectation is that when I create or update a user, the program only updates the references to the roles - not the roles. Do I need additional code for the transformation of the roles?

Or what can be the reason why Hibernate fails completely as soon as I want to change objects in a many-to-many relationship? I am thankful for every hint.

Code

@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(unique = true)
    @Size(max = 255)
    private String oid;

    @Size(max = 50)
    private String name;

    @Convert(converter = StringSetConverter.class)
    private Set<String> permissions;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), 
    inverseJoinColumns = @JoinColumn(name = "role_id"))
    @OrderBy("name")
    private Set<Role> roles;
    ...
}

@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Size(max = 50)
    private String name;

    @Convert(converter = StringSetConverter.class)
    private Set<String> permissions;
    ...
}

The processing code is kept very simple. I parse the JSON object and call em.merge() to update and em.persist() to create.

And with PostgreSQL I created the following tables:

CREATE TABLE "user" (
    id  SERIAL PRIMARY KEY,
    oid VARCHAR(255) UNIQUE,
    name VARCHAR(50),
    permissions TEXT
);

CREATE TABLE role (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    permissions TEXT
);

CREATE TABLE user_role (
    user_id INTEGER REFERENCES "user"(id),
    role_id INTEGER REFERENCES role(id),
    PRIMARY KEY (user_id, role_id)
);
André K
  • 41
  • 9

1 Answers1

0

After further research I came across the following post: JPA persist new entity with relationship to existing entity

Consequently, the roles were not known to JPA. I assumed that JPA would create a relationship based on the ID of the roles during the merge process. But it seems that I had to do this myself:

public User update(User user) {
    Set<Role> roles = new HashSet<Role>();
    for(Role role : user.getRoles()) {
        Role r = em.find(Role.class, role.getId());
        roles.add(r != null ? r : role);
    }
    user.setRoles(roles);
    user = em.merge(user);
    return user;
}
André K
  • 41
  • 9