1

Let's take a simple and well known example : Users and Groups.

An user can be in many groups, as a group can have many users. But in this case, we want to handle, for example, the date the user entered the group.

So we will have :

The table User :

@Entity
public class User extends com.example.UserItf {

    public User() { /* not needed */ }

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    @Override
    public Long getId(){
        return _id;
    }

    /* some other fields like name, etc. */

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
    @OrderBy("id")
    @Override
    public List<Group> getGroups(){
        return _groups;
    }
}

The table UserGroups :

@Entity
public class UserGroups extends com.example.UserGroupsItf {

    public UserGroups(){ /* not needed */ }

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    @Override
    public Long getId(){
        return _id;
    }

    @ManyToOne
    @JoinColumn(name="userid")
    @Override
    public User getUser() {
        return _user;
    }

    @ManyToOne
    @JoinColumn(name="groupid")
    @Override
    public Group getGroup() {
        return _group;
    }

    @Override
    public Date getEntryDate(){
        return _date; // only to represent a cross table with extra column
    }
}

The table Groups :

@Entity
public class Group extends com.example.GroupItf {

    public Group() { /* not needed */ }

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    @Override
    public Long getId(){
        return _id;
    }

    /* some other fields like name, etc. */

    @OneToMany(mappedBy = "group", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
    @OrderBy("id")
    @Override
    public List<User> getUsers(){
        return _users;
    }
}

My current scenario is the same and the problem I am facing is the following : I want to add for a selected user a group, so the steps (correct me if I'm wrong) are :

  1. Get the desired User (already done)
  2. Get the desired Group (from a List retrieved from the database)
  3. Add the Group to the User's List
  4. Persist the User

This will result in adding / updating an User with a Group, and so creates an entry in UserGroups as well, right ?

Now, I want to remove the Group from the User, so :

  1. Get the desired User (already done)
  2. Unselect the Group in the List
  3. This will remove from the User the Group (user.getGroups().remove(grp))
  4. Persist the User

My problem is that the entry in UserGroups is still existing. I've played around with Cascades, but I do not have the desired behavior : By removing a Group from the User, this also removes the Group in the table (UPDATE : It does not remove the Group, but it doesn't remove the link in UserGroups either).

I just want to delete the relation in the "cross table with extra column" if, from one side or another, I delete the link between User and Group.

I've looked around on Hibernate and StackOverflow, but nothing seems to fit my case. Note that the code for the tables I provided is exactly the code I have in my project.

Is it possible to delete the link only from the Users side for example, and cascade it to delete the UserGroups relation ? Or do I have to manage each entity separately ?

___ EDIT ___

Here is the Hibernate output when adding a Group to an User. Please note that this was an example, so I formatted the output to match the example, but nothing else has been modified.

DEBUG [org.hibernate.SQL] 
    insert 
    into
        UserGroups
        (groupId, entryDate, userId, userGroupsId) 
    values
        (?, ?, ?, ?)
TRACE [descriptor.sql.BasicBinder] binding parameter [1] as [BIGINT] - [1]
TRACE [descriptor.sql.BasicBinder] binding parameter [2] as [DATE] - [xxx] // added to match the example, this is an other field here (boolean)
TRACE [descriptor.sql.BasicBinder] binding parameter [3] as [BIGINT] - [1]
TRACE [descriptor.sql.BasicBinder] binding parameter [4] as [BIGINT] - [7]

And, surprisingely, after enabling the logging output, I see that there is no output for the remove. Is that possible that the EntityManager thinks the entity has not changed ?

The function to persist the user does the following :

_entityManager.getTransaction().begin();
_entityManager.persist( user );
_entityManager.getTransaction().commit();

Also, thanks to @Dragan Bozanovic, it seems that the scenario doesn't fit the problem. Here, nothing changes, nothing moves, so does the UserGroups link.

Jacks
  • 587
  • 5
  • 28
  • can you please post the code where you're removing the Group? – Raman Sahasi Feb 09 '16 at 10:38
  • it is just an example, but the code would be `user.getGroups.remove(grp)`, where `grp` is the selected group. Is that what you wanted ? – Jacks Feb 09 '16 at 10:49
  • In fact, I don't want to remove a Group. In my situation, imagine I only manage Users : Groups are constants in my database. So I just want to add a Group to an User or remove it, nothing else. – Jacks Feb 09 '16 at 10:52
  • Well, your examples do not reflect what you describe. Implement the examples, try them, and then post them describing what you observed. Also, enable [SQL logging](http://stackoverflow.com/a/2536835/4754790) and post the executed SQL. – Dragan Bozanovic Feb 09 '16 at 12:29
  • @DraganBozanovic please see edit. I'm not very familiar with Hibernate, if something is missing, please tell me. – Jacks Feb 09 '16 at 13:08
  • @Jacks instead of persist can you try with merge? – Madhusudana Reddy Sunnapu Feb 09 '16 at 13:18
  • @MadhusudanaReddySunnapu I just tried it, but nothing changes. – Jacks Feb 09 '16 at 13:29

2 Answers2

2

Just to highlight a bit my problem (and this could maybe save someone else life) my problem was just a coding issue.

I've seen in the code that in one entity I was using Hibernate's CascadeTypes, and on the other side I was using Java's cascade.

// This is the cascade provided by javax.persistence.CascadeType
@OneToMany(mappedBy = "group", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
@OrderBy("id")
@Override
public List<User> getUsers(){
    return _users;
}

VS

// This is the cascade provided by org.hibernate.annotations
@OneToMany(mappedBy = "group", fetch = FetchType.LAZY)
@Cascade(org.hibernate.annotations.CascadeType.ALL)
@OrderBy("id")
@Override
public List<User> getUsers(){
    return _users;
}

Of course, my problem was clearly my lack of knowledge about these, so maybe this can be an hint for someone else.

Jacks
  • 587
  • 5
  • 28
0

Is it possible to delete the link only from the Users side for example, and cascade it to delete the UserGroups relation ? Or do I have to manage each entity separately ?

It is possible to meet this requirement, but it looks like you need to update some of your code and the mappings.

Please go through the following link http://www.codejava.net/frameworks/hibernate/hibernate-many-to-many-association-with-extra-columns-in-join-table-example, I think it should help meet your requirement.

Dario
  • 23
  • 6
  • Thank you for your answer @MadhusudanaReddySunnapu, but in your example, they are using the join table to perform the actions like save or delete. I wanted to know if I could do it only from the User table (for example, getting an user and a group, creating an userGroup, add the userGroup to user, and persist only the user). – Jacks Feb 10 '16 at 08:34
  • And correct me if I'm wrong, but in your example, if you delete an UserGroup, it also deletes the User and the Group because of the CascadeType.ALL, which is not wanted. – Jacks Feb 10 '16 at 09:33
  • @Jacks As I test, the delete didn't result in deleting the User and Group entities. It only deletes the relationship. And regarding persisting only the user object, I doubt if that is possible with this mapping. – Madhusudana Reddy Sunnapu Feb 10 '16 at 11:09
  • That's weird ... I don't know why I have this kind of behavior but I'll do further tests and maybe I'll find the issue. I'll take your answer as the accepted answer for now, since the link you provided fits my case. Thank you. – Jacks Feb 10 '16 at 11:47