I have three tables - role, user, and user_role. This is supposed to be ManyToMany but since I also want to generate id for the user_role, I used OneToMany and ManyToOne.
Here are my entities with relevant fields only:
#Entity
public class Role {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "role")
private Set<UserRole> userRoles;
}
#Entity
public class User {
#Id
private String id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
private Set<UserRole> userRoles;
}
#Entity
public class UserRole {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
Then, this is how I create instances of them and save to the DB:
// Retrieve from DB by ID
Role role = ...;
// ID String is generated from UUID
User user = new User();
user.id("abc");
// ID String is generated from UUID
UserRole userRole = new UserRole();
userRole.setId("xyz");
Set<UserRole> userRoles = Set.of(userRole);
role.setUserRoles(userRoles);
user.setUserRoles(userRoles);
userRole.setUser(user);
userRole.setRole(role);
userRepository.save(user);
The issue that I find it difficult to resolve no matter how I have tried and googled:
2020-09-27 23:41:58.917 WARN 21948 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.example.entity.UserRole with id xyz; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.example.entity.UserRole with id xyz]
Please help me. Thanks.
Stern has good point. You are trying to save only user entity, but you don't have any cascade set anywhere. So when you call userRepository.save(user) it is obviously missing role entities. Either save dependent entities before saving user, or better, add cascade above userRoles field in your User class etc.
As mentioned elsewhere you need to se minimum:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user" , cascade = CascadeType.ALL)
private Set<UserRole> userRoles;
or minimum:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user" , cascade =
CascadeType.PERSIST)
private Set<UserRole> userRoles;
But also if you need to fetch UserRoles by User you need to set:
// for each UserRole in the list.
userRole.setUser(user);
before persisting otherwise list will not be populated.
Related
Hibernate appears to enforce mandatory relationships between entities. However, I have a User entity and a Portfolio entity. Each User can have 0 or 1 portfolios (but each Portfolio must have a User). As a result, a User should be able to delete a Portfolio without deleting the User entity. So far, I am unable to achieve this outcome. Instead, if a User deletes his/her Portfolio, the User also gets deleted.
How can I modify my annotations to achieve the desired outcome?
-User
/*
* Each User can have a Portfolio with many Accounts.
*/
#Entity
#Check(constraints = "LENGTH(TRIM(username)) > 0 &&"
+ " LENGTH(TRIM(username)) > 0 &&"
+ " LENGTH(TRIM(email)) > 0 &&"
+ " LENGTH(TRIM(password)) > 10")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Nationalized
#NotNull
private String username;
#Nationalized
#NotNull
private String password;
private String salt;
#Nationalized
#NotNull
private String email;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user" ,cascade = CascadeType.ALL)
private Portfolio portfolio;
// Not showing constructors, getters, or setters
}
-Portfolio
/*
* An Portfolio is a collection of accounts.
*/
#Entity
public class Portfolio {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "portfolio", cascade = CascadeType.ALL)
private List<Account> accounts = new ArrayList<>();
// Not showing constructors, getters, or setters
}
The problem is the cascade configured in your Portfolio entity.
By putting cascade = CascadeType.ALL you are instructing hibernate that when a Portfolio is deleted, it should delete the related User on cascade.
In this scenario, you can remove the cascade in that relationship, and keep the cascade in the User.portfolio relationship, because you want the Portfolio to be deleted when the User is deleted.
I currently have a problem with this Relationship, I have tried everything I saw on the internet. Still, I get this error: ERROR: column roles0_.user_id does not exist.
I have a boot app that has spring security, and I need to login using users from PostgreSQL database.
But I just can't get the relation between the user and the Role to work.
Here are Entity classes:
#Data
#Entity
#Table(name="user",schema = "public")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer id;
#Column(unique = true)
private String username;
private String password;
private boolean enabled;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Role> roles;
}
#Data
#Entity
#Table(name="role",schema = "public")
public class Role {
#Id
#Column(name="role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne()
#JoinColumn(name ="user_id")
private User user;
}
The database looks fine, I looked at the column names, etc. I don't know what to do to get rid of this error. I have the user table, and another table named roles, which include id and name, 2 inputs, USER and ADMIN...
It seems that the #JoinColumn annotation requires one additional column in the roles table the one with #ManytoOne relation, because when I add the column the error disappears, but when I'm trying to get the role from each user, I get an empty List. The foreign key is set as well, from the roles column to the role_id column from role table.
worked for me this way:
#Entity
#Data
#Table(name = "users")
public class User{
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")})
private List<Role> roles;
}
and then in roles just:
#Entity
#Table(name = "roles")
public class Role{
#ManyToMany(mappedBy = "roles", fetch = LAZY)
private List<User> users;
}
that's if you are ok with third table user_roles (user_id, role_id) which manages the many to many relation
User table :
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER)
private List<Role> roles;
Role table :
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
I'll put it simple. I've got User class and Privilege class. User has many Privileges.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Set<Privilege> privileges;
Privilege has one and only one User.
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
As you see I've specified CascadeType to ALL, but whenever I want to persist my User:
Set<Privilege> privs = new HashSet<>();
Privilege priv = new Privilege("anything");
//priv.setUser(user); it works with this line, of course
privs.add(priv);
user.setPrivileges(privs);
//session.save(user);
Privilege has not binded user.
Any ideas?
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="email", nullable = false, unique = true)
private String email;
#Column(name="password")
private String password;
#Column(name="user_type")
#Enumerated(EnumType.STRING)
private UserType userType;
#OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY, mappedBy = "user")
private Set<Privilege> privileges = new HashSet<>();
//getters, setters
#Entity
#Table(name = "privileges", uniqueConstraints = #UniqueConstraint(columnNames = {"user_id", "privilege"}))
public class Privilege {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "privilege")
private String privilege;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
//getters setters
To make sure I got your statement "Privilege has not binded user" . If you uncomment priv.setUser(user) then hibernate is able to associate user with the privilege in the database (i.e., user_id field is getting populated properly in the Privilege table). And if you comment out this line you don't see user_id being associated in the privilege table. Is that right?
If so, the reason is, you have specified mappedBy=user in the oneToMany annotation. This informs the hibernate that the association is mananged by the User field in the Privilege. So when hibernate is inserting the privilege record it looks into the user field to populate the userID.
With priv.setUser(user) hibernate would now know to which user this privilege has to be associated with and if you don't set this it will be null and you would see a null value against user_id column.
Or, let me know if I misinterpreted the question.
When you the Hibernate save process will causing a ACTION_SAVE_UPDATE action, but the JPA will pass a ACTION_PERSIST and ACTION_MERGE, it will not match and causing the cascade failed to execute.
If you delete the JPA cascade – javax.persistence.CascadeType, replace it with Hibernate cascade – org.hibernate.annotations.Cascade, with CascadeType.SAVE_UPDATE.
It must work.
I have 3 tables:
user: (id,username,password)
role: (id,role)
user_roles: (user_id, role_id)
and the following two Hibernate entities:
User:
#Entity
#Table(name = "user")
public class User implements Serializable{
#Id
#GeneratedValue
private Long id;
private String userName;
private String password;
private boolean active = false;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER )
#JoinTable(name = "user_roles", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "role_id", referencedColumnName = "id") })
private Set<Role> roles = new HashSet<Role>();
....
Role:
#Entity
#Table (name = "roles")
public class Role implements Serializable {
#Id #GeneratedValue
private Long id;
private String role;
....
When I delete now a User the corresponding role get deleted as well from the "role" table instead to delete just the user row and the user_roles relation. Even if other users are still related to that role. I use the following to delete the user.
#Transactional
public void deleteByName(String userName) {
User user = this.getByName(userName);
Session session = sessionFactory.getCurrentSession();
session.delete(user);
}
Anyone know why and how to solve this?
Thanks
This is the expected behaviour for the current mapping. It is configured by the cascade setting:
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER )
private Set<Role> roles = new HashSet<Role>();
If you do not want the roles to be removed when a user is, you will have to narrow down your cascade settings. Right now you are using ALL. This is equivalent to {PERSIST, REMOVE, REFRESH, MERGE, DETACH}. Decide on which ones you need and remove the others.
There is one thing I did not understand. You say that the roles are removed "Even if other users are still related to that role". This should not be possible.
The relationship of Role and User is a many-to-one, so there can be only one user attached to a role.
I am trying to use an #JoinColumn as an #Id using JPA and I am getting SerializationExceptions, "Could not serialize."
UserRole.java:
#Entity
#Table(name = "authorities")
public class UserRole implements Serializable {
#Column(name = "authority")
private String role;
#Id
#ManyToOne
#JoinColumn(name = "username")
private User owner;
...
}
User.java:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
protected Long id;
#Column(name = "username")
protected String email;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<UserRole> roles = new HashSet<UserRole>();
....
}
"username" is set up as a unique index in my Users table but not as the primary key.
Is there any way to make "username" act as the ID for UserRole? I don't want to introduce a numeric key in UserRole. Have I totally lost the plot here?
I am using MySQL and Hibernate under the hood.
That mapping doesn't really make sense. ID has to be unique, but ManyToOne says 'lots of these have the same User.'