Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the work-around for deleting orphan entities using JPA 2.0 and @OneToMany?

I'm using JPA 2.0, Hibernate 4.1.0.Final, Spring 3.1.1.RELEASE, and Java 1.6. I have this entity with a one-to-many relationship to another entity …

import javax.persistence.CascadeType;
...
@Entity
@Table(name = "classroom")
public class Classroom implements Serializable
{
    ...

    @OneToMany(mappedBy = "classroom", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
    private Set<ClassroomUser> roster;

However, when I update my entity with a different set of ClassroomUser objects

classroom.setRoster(newRoster);

and save the entity, all the previous ClassroomUser objects remain. What is the proper/shortest way to update my entity while removing all the orphan records from the database?

Thanks, - Dave

like image 694
Dave Avatar asked Mar 24 '23 15:03

Dave


1 Answers

Use orphanRemoval:

@OneToMany(mappedBy="classroom", cascade={CascadeType.ALL}, orphanRemoval=true)

Whenever an entry is removed from the persistent set, it will get deleted. And this means you need to work with the persistent set. I.e. you are not allowed to replace the set, instead you should do:

classroom.getRoster().clear();
classroom.getRoster().addAll(newRoster);

EXAMPLE how to synchronize persistent set with a user required set:

/**
 * Assemble ClassroomUser relations.
 * @param classroom Classroom entity. Must be attached persistent or transient. Never null.
 * @param userIds Collection of user identifiers. Can be empty. Never null.
 */
private void assembleClassroomUsers(Classroom classroom, Collection<Integer> userIds) {
    // Make sure relation set exists (might be null for transient instance)
    if (classroom.getUsers() == null) {
        classroom.setUsers(new HashSet<ClassroomUser>());
    }
    // Create working copy of the collection
    Collection<Integer> ids = new HashSet<Integer>(userIds);
    // Check existing relations and retain or remove them as required
    Iterator<ClassroomUser> it = classroom.getUsers().iterator();
    while (it.hasNext()) {
        Integer userId = it.next().getUser().getId();
        if (!ids.remove(userId)) {
            it.remove(); // This will be picked by the deleteOrphans=true
        }
    }
    // Create new relations from the remaining set of identifiers
    for (Integer userId : ids) {
        ClassroomUser classroomUser = new ClassroomUser();
        classroomUser.setClassroom(classroom);
        // User must not have ClassroomUser relations initialized, otherwise Hibernate 
        // will get conflicting instructions what to persist and what to drop => error.
        // It might be safer to use dummy transient instance...
        User dummyUser = new User();
        dummyUser.setId(userId);
        classroomUser.setUser(dummyUser);
        classroom.getUsers().add(classroomUser);
    }
}

This approach might seem a little bit complex. You might be able to create something simpler (but probably not too much) with custom equals/hashCode and some Set<E> manipulation methods (e.g. from Guava).

like image 111
Pavel Horal Avatar answered Apr 05 '23 22:04

Pavel Horal