Hibernate OneToMany mapping with cascade - java

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.

Related

Spring boot #JoinCulumn ManyToOne relationship column does not exist

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;

Spring Data JPA ManyToOne and OneToMany - Unable to find entity with ID

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.

What is the most efficient way to map #ManyToMany relationship with JPA and Hibernate?

I know only basics of DB and JPA/Hibernate. I have to manage a User table, where a user can have many roles. The roles are contained in a catalog table which in my User formulary i do not pretend to manage/modify, i just need the catalog values as a reference to add or delete to my user.
I think the best approach would be to create a relationship table between User and Role to hold the users and their roles 'User_Roles' (unless there is a more efficient approach).
I am not allowed to modify the Role entity since it is used for different purposes in a lot of other areas of my app that are independent of the User table.
I've seen a lot of examples but I still do not know which one exactly aplies to my specific needs. How can I map my User and its roles in a sigle Entity with JPA and Hibernate?
Maybe the next image describes better what I want:
Thank you very much in advance for your answers.
In your case you have to use #ManyToMany to associate both tables.
That should look at this:
#Entity
#Table(name = "User")
public class User {
...
#ManyToMany
#JoinTable(name = "User_Roles", joinColumn = "id_person")
private Set<Role> roles = new HashSet<>;
}
#Entity
#Table(name = "Role")
public class Role {
...
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>;
}
What you're describing is a one-to-many relationship but it's between User and the joining table - User_Roles. Since there is not much you can do to avoid the joining table, the best thing would be to use #ManyToMany with #JoinTable annotations to map the relationship. Remember to use Set instead of List. You don't need an entity for the joinint table then.
You can find a discussion about this topic in this blog post.
As per your above screen, what I understood user can be assigned more than 1 role.
i.e. 1 user can be mapped to multiple role and 1 role can be mapped to multiple users.
Hence relationship between user and role is many to many.
many to many relationship can be achieved using third table which is called mapping table.
so , we have following tables in your example :-
user
user_roles
role
#Entity
#Table(name = "user")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
#Id
#SequenceGenerator(name = "USER_ID_GENERATOR", sequenceName = "USER_SEQ",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_ID_GENERATOR")
#Column(name = "user_id")
private Long userId;
#OneToOne
#JoinColumn(name = "persion_id")
private person person;`
enter code
here`
#Basic
#Column(name = "date")
private Date date;
#Basic
#Column(name = "observations")
private String observations;
#Basic
#Column(name = "text")
private String text;
#JsonIgnore
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserRoles> users = new ArrayList<>();
}
#Entity
#Table(name = "role")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Role {
#Id
#SequenceGenerator(name = "ROLE_ID_GENERATOR", sequenceName = "ROLE_SEQ",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ROLE_ID_GENERATOR")
#Column(name = "role_id")
private Long roleId;
#Basic
#Column(name = "id1")
private Long idOne;
#Basic
#Column(name = "id1")
private Long idTwo;
#Basic
#Column(name = "id1")
private Long idThree;
#Basic
#Column(name = "text")
private String text;
#JsonIgnore
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL)
private List<UserRoles> users = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name = "user_roles")
#JsonInclude(JsonInclude.Include.NON_NULL)
#Audited
public class UserRoles {
private static final long serialVersionUID = 1L;
#EmbeddedId
UserRolesKey userRoleId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("role_id")
#JoinColumn(name = "role_id")
Roles role;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(user_id)
User user;
#PrePersist
private void prePersist() {
super.onPrePersist();
if (this.getId() == null) {
UserRolesKey mapKey = new UserRolesKey();
mapKey.setRoleId(this.getRole().getRoleId());
mapKey.setUserRoleId(this.getUser().getUserId());
this.setId(mapKey);
}
}
}
While saving you just need to populate user entity with all the uaerRoles mapping entity and persist it. jpa will save all the details.
while updating role assign to user you need to fetch the user entity and update the mapping by adding new userRoles entity and nullifying the while is going to be removed.

Lazy fetch type doesn't work for identical objects

There is User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
...
#Column(nullable = false)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Set<UserPermission> permissions;
}
There is permission entity:
#Entity
#Table(name = "user_permissions", indexes = {#Index(unique = true, columnList = "id")})
public class UserPermission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
public User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "target_user_id", referencedColumnName = "id")
public User targetUser;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "other_entity", referencedColumnName = "id")
public OtherEntity otherEntity;
}
I use this code to fetch user entity:
... get session
... open transaction
// fetching user
final User user = session.find(User.class, id);
// fetching permissions
final Set<UserPermission> userPermissions = user.permissions;
// foreach permissions
// HIBERNATE FETCHES HERE targetUser AND EVERYTHING CONNECTED TO THIS ENTITY, LOOKS LIKE FetchType.EAGER
for (UserPermission permission: userPermissions) {
// there is no code
}
... close transaction
Look at the comment:
// HIBERNATE FETCHES HERE targetUser AND EVERYTHING CONNECTED TO THIS ENTITY, LOOKS LIKE FetchType.EAGER
It means, that without any calls to targetUser, hibernate fetches it. That's really strange. This behavior really slows down performance of my application.
My assumption: Hibernate just can't proxy two similar objects in one entity (i mean, user and targetUser have the same class, hibernate cannot proxy both objects and thats why it fetches targetUser immediately)
How to solve this problem? Any suggestions?

While deleting a user entity in hibernate the role get deleted as well

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.

Categories

Resources