Search code examples
springhibernatejpa-2.0nhibernate-mapping

Transitive Persistence & "detached entity passed to persist"


I think (hopefully) I'm doing all the right things. Googled it a lot, couldn't find what's wrong.

Profile can have multiple Appointments. Each Appointment has a Department.

  • Departments are loaded and stored in a set, of course, detached.
  • Appointments are created and get a Department from the set mentioned above.
  • Appointments are added to Profile.
  • Profile is made persistent.
  • detached entity passed to persist is thrown for Department

Profile:

@Entity
@Table(name = "PROFILE")
public class Profile {
/**
 * 
 */
private Set<Appointment> appointments = new HashSet<Appointment>();

/**
 * @return the appointments
 */

@OneToMany(mappedBy = "profile", cascade = { CascadeType.PERSIST,
        CascadeType.MERGE })
public Set<Appointment> getAppointments() {
    return appointments;
}

/**
 * @param appointments
 *            the appointments to set
 */
public void setAppointments(Set<Appointment> appointments) {
    this.appointments = appointments;
}

/**
 * 
 * @param apt
 */
public void addAppointment(Appointment apt) {
    apt.setProfile(this);
    appointments.add(apt);
}
}

Appointment:

@Entity
@Table(name = "APPOINTMENT")
public class Appointment {
/**
 * 
 */
private Department department;
private Profile profile;


/**
 * @return the department
 */
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
@JoinColumn(name = "DEPARTMENT_ID")
@Index(name = "IDX_APPOINTMENT_DEPARTMENT")
@ForeignKey(name = "FK_APPOINTMENT_DEPARTMENT")
public Department getDepartment() {
    return department;
}

/**
 * @param department
 *            the department to set
 */
public void setDepartment(Department department) {
    this.department = department;
}

/**
 * @return the profile
 */
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY)
@JoinColumn(name = "PROFILE_ID")
@Index(name = "IDX_APPOINTMENT_PROFILE")
@ForeignKey(name = "FK_APPOINTMENT_PROFILE")
public Profile getProfile() {
    return profile;
}

/**
 * @param profile
 *            the profile to set
 */
public void setProfile(Profile profile) {
    this.profile = profile;
}
}

Department:

@Entity
@Table(name = "DEPARTMENT")
public class Department {
/**
 *
 */
private Set<Appointment> appointments = new HashSet<Appointment>();
private Set<Department> children = new HashSet<Department>();
private Department parent;


/**
 * @return the appointments
 */
@OneToMany(mappedBy = "department")
public Set<Appointment> getAppointments() {
    return appointments;
}

/**
 * @param appointments
 *            the appointments to set
 */
public void setAppointments(Set<Appointment> appointments) {
    this.appointments = appointments;
}

/**
 * @return the children
 */
@OneToMany(mappedBy = "parent", cascade = { CascadeType.PERSIST,
        CascadeType.MERGE })
public Set<Department> getChildren() {
    return children;
}

/**
 * @param children
 *            the children to set
 */
public void setChildren(Set<Department> children) {
    this.children = children;
}

/**
 * @return the parent
 */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PARENTDEPARTMENT_ID")
@Index(name = "IDX_DEPARTMENT_CHILDREN")
@ForeignKey(name = "FK_DEPARTMENT_CHILDREN")
public Department getParent() {
    return parent;
}

/**
 * @param parent
 *            the parent to set
 */
public void setParent(Department parent) {
    this.parent = parent;
}
}

The errant @Service code:

@Transactional(propagation = Propagation.REQUIRED)
private Profile addNewProfile(ProfileJxb profileJxb) {
    logger.entry();

    Profile profile = new Profile(profileJxb);
    for (AppointmentJxb aptJxb : profileJxb.getAppointments()
            .getAppointmentJxb()) {
        Appointment apt = new Appointment(aptJxb);
        Department d = getDepartmentByName(aptJxb.getDepartment()); // previously fetched
        apt.setDepartment(d);
        // d.getAppointments().add(apt); // another failed attempt, this results in LazyInitException

        profile.addAppointment(apt);
    }

    profile = profileDao.makePersistent(profile); // Exception thrown here!

    logger.exit();
    return profile;
}

I hope I'm missing something, as I'm fetching and setting Department in a persistent state to avoid this exception.

Thank you.


Solution

  • When you need to save a new entity that references detached entities via cascaded relationships you have to use merge() instead of persist(), but it's not the best way to solve this problem.

    The real problem is that department relaionship has cascading at all. Cascading makes sense when referring to entities that the entity in question logically own. But in your case you have a predefined set of Departments that is managed separately. Therefore you never need to cascade any operation from Appointment to Department.

    So, the best solution is to remove cascading from department relationship.