Hibernate remove relation manyToMany - java

I have 3 tables Role, User, and UserRole. The table UserRole contains the mapping between user and role along with the two corresponding index columns. I am using hibernate with annotations, and would like to be able to "Revoke" a role from the user, but this is turning out to be somewhat difficult.
In my User Class I have
#ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, targetEntity = Role.class)
#IndexColumn(name = "role_index", base = 0)
#NotFound(action=NotFoundAction.IGNORE)
#JoinTable(name = "tblUser_Role", joinColumns={
#JoinColumn(name = "UID")}, inverseJoinColumns={
#JoinColumn(name = "roleid", nullable = false)})
private List<Role> roles = new ArrayList<Role>(0);
In my Role class I have
#ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, mappedBy="roles")
private List<User> users = new ArrayList<User>(0);
and the DAO method I am calling to "Revoke" the role(s) is
#Override
public boolean revokeRolesFromUserAccount(User user, List<Role> userRoles) {
if (log.isInfoEnabled()) {
log.info("Roles revoked from the User " + user.getUsername());
}
if (user == null) {
return false;
}
if (userRoles == null) {
return false;
}
Iterator<Role> iter = userRoles.iterator();
List<Role> newroles = new ArrayList<Role>(0);
Role role = null;
while (iter.hasNext()) {
role = (Role) this.sessionFactory.getCurrentSession().load(
Role.class, iter.next().getRoleid());
newroles.add(role);
}
User newUser = null;
newUser = (User) this.sessionFactory.getCurrentSession().load(User.class, user.getUid());
newUser.getRoles().removeAll(newroles);
this.sessionFactory.getCurrentSession().saveOrUpdate(newUser);
return true;
}
for some reason this does not work as expected, when breaking through I noticed the roles were not being initialized I guess due to the LazyLoading, and I tried doing something like Hibernate.initialize(newUser.getRoles()) but this did not change anything. I am still learning the ropes with hibernate and am not sure what I am missing, maybe something very obvious?? Thank you so much for your time and thoughts in advance!
UPDATE: After trying the fixes suggested by Paujo and Matin Kh and doing further debugging I still have not seen any differences in the Roles being loaded in after line newUser = (User) this.sessionFactory.getCurrentSession().load(User.class, user.getUid());
Here is a copy of my tblUser_Role, not sure if this helps. Thanks again!
(Adding roles works just fine)

I'm having the exact situation here. Your question has a very simple solution.
For one of your classes use EAGER, and for the other one use LAZY. Try this:
Role:
#ManyToMany(fetch=FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, mappedBy="roles")
private List<User> users = new ArrayList<User>(0);
User:
#ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST}, targetEntity = Role.class)
#IndexColumn(name = "role_index", base = 0)
#NotFound(action=NotFoundAction.IGNORE)
#JoinTable(name = "tblUser_Role", joinColumns={
#JoinColumn(name = "UID")}, inverseJoinColumns={
#JoinColumn(name = "roleid", nullable = false)})
private List<Role> roles = new ArrayList<Role>(0);

I think your approach should be different.
UserRole should have a role name.
Your User should have a list of UserRole items or just one UserRole but with a hierarchy of UserRole. But as you have a list that is ok.
So basically you have references in User to UserRole so there is no need to have references to User in UserRole.
That way your table becomes very simple and you dont need any joins to do it and you can then just select UserRole by name.
After that you should be able to remove the reference from the list of UserRole in User and merge the User.

Finally got this working, mostly by sheer luck, this is what I ended up with in my RoleDaoImpl class.
#Override
#Transactional
public boolean revokeRolesFromUserAccount(User user, List<Role> userRoles) {
if (user == null) {
return false;
}
if (userRoles == null) {
return false;
}
User newUser = null;
newUser = (User) this.sessionFactory.getCurrentSession().load(User.class, user.getUid());
List<Role> newRoleList = newUser.getRoles();
newRoleList.removeAll(userRoles);
if(newUser.getRoles().retainAll(newRoleList)){
if (log.isInfoEnabled()) {
log.info("Roles revoked from the User " + user.getUsername());
}
this.sessionFactory.getCurrentSession().saveOrUpdate(newUser);
return true;
}else{
return false;
}
Hope this can be of some use to others in the future!
Thanks again for everyone's help!

Related

Fix adding entites to ManyToMany

I need to check if user's email is already added to list of students and if so, connect this user with course.
java.lang.UnsupportedOperationException
at java.util.Collections$1.remove(Collections.java:4684)
at java.util.AbstractCollection.clear(AbstractCollection.java:436)
at org.hibernate.collection.internal.PersistentSet.clear(PersistentSet.java:318)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:581)
at org.hibernate.type.CollectionType.replace(CollectionType.java:757)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:167)
User Entity
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(
name = "Student_Courses",
joinColumns = {#JoinColumn(name = "student_id")},
inverseJoinColumns = {#JoinColumn(name = "course_id")}
)
private Set<Course> availableCourses = new HashSet<>();
Course Entity
#ManyToMany(mappedBy = "availableCourses")
private Set<User> users = new HashSet<>();
UserService
public void bindStudentWithCoursesAfterRegistration(String email) {
User user = userRepo.findFirstByEmail(email);
List<CourseStudentEmails> studentEntriesInCourses = courseStudentEmailsRepo.findAllByEmail(email);
if (studentEntriesInCourses.size() != 0){
for (CourseStudentEmails entry : studentEntriesInCourses) {
Course course = entry.getCourse();
user.getAvailableCourses().add(course);
}
}
userRepo.save(user);//Exception throws here
}
Registration Controller
#PostMapping("/registration")
public String addUser(User user, #RequestParam(value = "checkboxTeacher", required = false) String checkboxValue, Model model) {
[//chek if user is already registered]
userRepo.save(user);
courseService.bindStudentWithCoursesAfterRegistration(user);
return "redirect:/login";
}
I guess you're missing a proper query to find appropriate CourseStudentEmails.
If you keep a collection of String as emails in your entity (CourseStudentEmails), you can query entities based on specific email as this:
#Query("SELECT c FROM CourseStudentEmails c WHERE ?1 member of c.emails")
List<CourseStudentEmails> findAllByEmail(String email);

Fetch Many to Many collection using Hibernate Criteria

I have two entities User and Attribute and they have Many to Many relation. like below.
public class User implements java.io.Serializable {
private Set<Attribute> attributes = new HashSet<>();
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "user_attributes", joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "attribute_id") })
public Set<Attribute> getAttributes() {
return attributes;
}
}
User_Attributes table may or may not contain data. I am trying to fetch user like below
public User getUserByUserNameAndStatus(String userName, int status) {
Criteria criteria = getSession().createCriteria(User.class);
criteria.createAlias("attributes", "attr", JoinType.LEFT_OUTER_JOIN);
criteria.setFetchMode("roles", FetchMode.JOIN);
criteria.add(Restrictions.eq("attr.status", 1));
criteria.add(Restrictions.eq("userName", userName));
criteria.add(Restrictions.eq("status", status));
User user = (User) criteria.uniqueResult();
return user;
}
This code return me user object only if there is an entry in User_Attributes table otherwise it is returning me null. But what I want to fetch user object also if there is no entry in User_Attributes table.

Retrieve data from many-to-many relationship - Hibernate

I have a 'User' class:
public class User implements Serializable {
#Id
#GeneratedValue
int id;
String nome;
#Column(unique = true)
String email;
#ManyToMany
#JoinTable (name = "user_roles", joinColumns=
{ #JoinColumn (name = "user_id")}, inverseJoinColumns=
{ #JoinColumn (name = "role_id")})
private List<Role> roles;
I have a 'Role' class:
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
int id;
#Column(unique = true)
String role;
Relationship :An User has roles, and roles has users. I have a many-to-many relationship here. The relation is unidirectional, and my dominant entity is User.
In my database has a table named "user_roles", created automatically. I have a User registered with two roles.
When I retrieve the User from my database using a "UserDao" class, and I try to access the roles of the user, I get NullPointerException. I got all others informations (name, email, etc), but the roles I can not access.
My 'UserDao' class:
public class UserDaoImpl implements UserDao{
private static final transient Logger log = LoggerFactory.getLogger(UserDaoImpl.class);
private final String HIBERNATE_CFG = "data.cfg.xml";
#Override
public int insertUser (User user){
int code = 0;
Session session = new HibernateUtil(HIBERNATE_CFG).getSessionFactory().getCurrentSession();
try {
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
code = 1;
}
catch (Exception e) {
log.error(e.getMessage());
session.getTransaction().rollback();
code = 0;
}
return code;
}
#Override
public List<User> getAll() {
Session session = new HibernateUtil(HIBERNATE_CFG).getSessionFactory().getCurrentSession();
List<User> users = null;
try
{
session.beginTransaction();
String queryString = "SELECT * FROM USER;";
SQLQuery consulta = session.createSQLQuery(queryString);
consulta.setResultTransformer(Transformers.aliasToBean(User.class));
users = (List<User>) consulta.list();
session.getTransaction().commit();
}
catch(ConstraintViolationException e)
{
log.error(e.getMessage());
session.getTransaction().rollback();
}
return users;
}
I tried using iterator:
List roles = user.getRegras();
for (Iterator it = roles.iterator(); it.hasNext();){
Role r = (Role) it.next();
out.println("Role:" + r.getRole());
}
I tried to instantiate the variable "roles" in the 'User' class like below. But every time the variable returns size 0:
#ManyToMany
#JoinTable (name = "user_roles", joinColumns=
{ #JoinColumn (name = "user_id")}, inverseJoinColumns=
{ #JoinColumn (name = "role_id")})
private List<Role> regras = new ArrayList<Role>();
I tried getting the roles from Object User, but I receive a NullPointerException when the method size() is called.
List<Role> roles = (List<Role>)user.getRoles();
out.println("size roles: " + roles.size());
I'm trying to acess my table 'user_roles' to retrieve the data from database, but I can't dothis. Can anyone help me?
Thanks in Advance
You're using a SQL query to load the users, and mapping the result to the entity. So what you get is detached entities, containing only what the SQL query returned.
Learn to use JPQL/HQL, and to load entities, which will then be managed:
String queryString = "SELECT u FROM User u";
Query consulta = session.createQuery(queryString);
users = (List<User>) consulta.list();

Hibernate Criterion - join between more tables

I've got a problem with this situation (i'll try to make it easier) - there are users in my DB with list of roles and list of statuses.
public class User implements Serializable {
#ManyToMany(cascade = CascadeType.ALL, fetch= FetchType.LAZY)
#JoinTable(name = "role_to_user",
joinColumns={#JoinColumn(name = "user_id")},
inverseJoinColumns={#JoinColumn(name = "role_id")})
private Set<Role> roles = new LinkedHashSet<Role>();
#ManyToMany(cascade = CascadeType.ALL, fetch= FetchType.LAZY)
#JoinTable(name = "status_to_user",
joinColumns={#JoinColumn(name = "user_id")},
inverseJoinColumns={#JoinColumn(name = "status_id")})
private Set<Status> statuses = new LinkedHashSet<Status>();
}
I am trying to create a hibernate criteria, which was abble to return users (after join with role table and status table) with specified roles and statuses. Something like :
return users with role = 1 or role = 2 and status = 1
I googled something and now I can create a query which returns me users only with specified roles, but not statuses.
Criteria cr = session.createCriteria(User.class)
.createCriteria("roles")
.add(Restrictions.or(Restrictions.eq("roletype", 1), Restrictions.eq("roletype", 2)));
Table user and role are connected through role_to_user table with two columns (user_id, role_id), similarly in the same way table user and status through status_to_role table
Thanks for advice :)
Criteria cr = session.createCriteria(User.class, "user")
.createAlias("user.roles", "role", Criteria.INNER_JOIN)
.createAlias("user.statuses", "status", Criteria.LEFT_JOIN);
cr.add(Restrictions.or(
Restrictions.eq("role.roletype", 1),
Restrictions.and(
Restrictions.eq("role.roletype", 2),
Restrictions.eq("status.statusType", 1))));

#ElementCollection, #CollectionTable and Enum - Strange delete/insert behavior

#Entity
public class User{
#ElementCollection
#Enumerated(EnumType.STRING)
#CollectionTable(name = "SEC_USER_ROLES",
joinColumns =
#JoinColumn(name = "USER_ID", referencedColumnName = "ID"))
#Column(name = "ROLE_NAME")
private List<Role> roles;
[...]
}
public enum Role {
ROLE_SUPER_ADMIN,
ROLE_ADMIN,
ROLE_ARB,
ROLE_AP;
[...]
}
With this mapping, when I try do delete one ROLE, for example ROLE_ARB, it always ends up with deleting the role and inserting it once again.
DELETE FROM SEC_USER_ROLES WHERE ((USER_ID = ?) AND (ROLE_NAME = ?))
bind => [9451, ROLE_ADMIN]
INSERT INTO SEC_USER_ROLES (USER_ID, ROLE_NAME) VALUES (?, ?)
bind => [9451, ROLE_ADMIN]
I tried to solve the problem with #OrderColumn (name="USER_ID") but then the mapping of the User_id is not correct.
Any idea would be appreciated.
The Roles are represented as selectManyCheckbox
The ManagedBean prepares the entity (User)
...
List<String> selectedroles = this.getSelectedItems();
List<Role> newroles = new ArrayList<Role>();
if (selectedroles != null) {
for (String r : selectedroles) {
newroles.add(Role.valueOf(r));
}
getEntity().setRoles(newroles);
...
security.save(getEntity());
and the EJB makes updates if it is an existing Entity
EntityManager em;
...
this.em.merge(user);
So when someone deselects all (previous selected) Roles there is always one Role left in the database which is not deleted, because of the delete/insert behavior I described before.
#OrderColumn solved the problem

Categories

Resources