How to cascade update to child entity in Hibernate

Igor RMS :

I have three entities: Credentials, User and Admin. Both User and Admin entities have a field credentials, which is related to Credentials entity using OneToOne annotation.

When updating an existing User or Admin entry, through entityManager.merge, I am getting duplicated key over Credentials.login column, which has unique constraint.

@Entity
@Table
public class Credentials {
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique=true, nullable=false, length=50)
    private String login;

    @Column(nullable=false, length=50)
    private String password;

    /*********************************************
    *  getters and setters here
    **********************************************/
}

@Entity
@Table
public class User{
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /*********************************************
    *  Specific user columns here
    **********************************************/

    @OneToOne(cascade = {CascadeType.ALL})
    @JoinColumn(nullable=false, name = "idCredentials")
    private Credentials credentials;

    /*********************************************
    *  getters and setters here
    **********************************************/
}

@Entity
@Table
public class Admin{
    @Id
    @Column(name="id", unique=true, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /*********************************************
    *  Specific admin columns here
    **********************************************/

    @OneToOne(cascade = {CascadeType.ALL})
    @JoinColumn(nullable=false, name = "idCredentials")
    private Credentials credentials;

    /*********************************************
    *  getters and setters here
    **********************************************/
}

I expect to have user and user.credentials updated in respective database tables after calling entityManager.merge(user), but I am getting the error "Duplicate entry 'loginname' for key 'login_UNIQUE. The same happens with Admin entity.

Thank you in advance for any help.

Marco R. :

The reason you may be getting this error is due to the fact that merge copies the contents of a detached entity into a managed entity. So, if you are passing as an argument to merge a User or Admin entity (from now on referred as PERSON) that contains a detached copy of a Credentials entity persisted in the DB; then you will definitely be getting this problem. The reason for this is:

  1. merge will copy the entire state of the PERSON entity argument into the corresponding context managed PERSON entity.
  2. This copy will include the Credentials entity inside the PERSON argument.
  3. Since the Credentials entity was detached, the persistence manager will assume this entity corresponds to an entity not persisted yet.
  4. The persistence context, when flushing the session, will save the merged Credentials using an INSERT (persist the new Credentials) rather than an UPDATE.
  5. The INSERT will trigger the duplicate constrain violation on the login field you are getting, because the original Credentials record exist with the login value being used in the INSERT.

EDIT: (How to merge)

If you are not updating the Credentials in PERSON, then when merging in your PERSON_DAO, you could:

  1. Temporarily remove Credentials from PERSON (null) before merging.
  2. Merge PERSON.
  3. Add the original Credentials back to your newly merged PERSON

Since I have no access to your DAO code, here is a pseudo code of the previous:

    public PERSON mergeSafely(PERSON person) {
        Credentials originalCredentials = person.getCredentials();
        person.setCredentials(null);
        person = em.merge(person);
        person.setCredentials(originalCredentials);
        return person;
    }

In case you want to merge also the Credentials, then you should (for cleanness sake) use the service layer above the dao layer. A pseudo code of a utility method to accomplish this in that layer would look like:

    public PERSON mergeCompletely(PERSON person) {
        Credentials mergedCredentials = credentialsDAO.merge(person.getCredentials());
        person.setCredentials(mergedCredentials);
        person = personDAO.merge(person);
        return person;
    }

Hope this helps.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=96052&siteId=1