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)
);