Search code examples
javaspring-boothibernatespring-data-jpahibernate-mapping

How to update Child entity along with Parent entity in Spring Boot?


I am trying to update the parent entity and child entity (comes with parent) using JPA. I tried many ways but I couldn't do it. It gives me the following error.

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session Contact(6)

Following is the entity,

Parent -> User.java

@Entity
public class User extends Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_generator")
    @SequenceGenerator(name = "seq_generator", sequenceName = "seq_users", initialValue = 1, allocationSize = 1)
    private Long id;
    
    private String firstName;
    private String lastName;
    private String email;
    private String password;
    private String street;
    private String city;
    
    @OneToOne(cascade = CascadeType.ALL, mappedBy = "userContact")
    private Contact contact;
}

Child -> Contact.java

@Entity
public class Contact extends Serializable {

    private static final long serialVersionUID = 1L;
    
    @Getter(value = AccessLevel.NONE)
    @Setter(value = AccessLevel.NONE)
    @Id
    @Column(insertable = false, updatable = false)
    private Long id;

    @MapsId
    @OneToOne
    @JoinColumn(name = "id", nullable = false)
    private User userContact;

    private String phone;
}

In my Service.java I am updating like this,

@Override
@Transactional
public User update(Long id, User user) {
    Optional<User> curUser = userRepo.findById(id);
    
    user.setId(curUser.get().getId());
    user.setStreet(curUser.get().getStreet());
    user.setCity(curUser.get().getCity());

    Contact contact = user.getContact();
    contact.setUserContact(user);

    user.setContact(contact);

    return userRepo.save(user);
}

And the update JSON payload is like this,

{
    "firstName": "FirstName",
    "lastName": "LastName",
    "email": "Email",
    "password": "Password",
    "contact": {
        "phone": "0123456789"
    }
}

So why I can't update this at once? The insert is working fine but this update is not working. What I am doing wrong here? I really appreciate it if anybody can help me. Thanks in advance.

Here is the new way I tried,

@Override
@Transactional
public User update(Long id, User user) {
    Optional<User> curUser = userRepo.findById(id);
    
    curUser.setFirstName(user.getFirstName());
    curUser.setlastName(user.getLastName());
    curUser.setEmail(user.getEmail());
    curUser.setPassword(user.getPassword());

    Contact contact = user.getContact();
    contact.setUserContact(curUser);

    curUser.setContact(contact);

    return userRepo.save(curUser);
}

Solution

  • Because you have the same user (i.e. same id) represented twice in the persistence context.

    This line loads the user curUser in the persistence context.

    Optional<User> curUser = userRepo.findById(id);
    

    This line makes user the same user by id, but it is a different instant.

    user.setId(curUser.get().getId());
    

    And finally this line tries to add user to the persistence context.

    userRepo.save(user);
    

    This would result with the two instances being attached to the persistence context while representing the same user with the same id which would result in an inconsistent state since JPA wouldn't know which version to persist. Therefore this is forbidden and throws the exception you are seeing.

    In your case changing

    Optional<User> curUser = userRepo.findById(id);
        
    user.setId(curUser.get().getId());
    

    to

    user.setId(id);
    

    should do the trick.

    Regarding your comments:

    The same problem exists for referenced objects. You'll have to make sure that you: either load an entity and change it's attribute, or do NOT load the entity, not directly nor indirectly via a reference and save a detached entity, constructed from your request.

    If you load an entity and then try to save a detached instance of it, you will get this kind of error.