Is there a way to pass detached object to JPA persist? (detached entity passed to persist)

27,931

Solution 1

Just replace the

entityManager.persist(account);

with:

entityManager.merge(account);

And allow merge cascading:

@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER)
public AccountRole getAccountRole() {
    return accountRole;
}

Because merge does this:

If your entity is new, it's the same as a persist(). But if your entity already exists, it will update it.

Solution 2

It looks like you leave the transaction during your processing, so the accountRole gets detached, or it is already detached for other reasons.

A call to entityManager.merge(accountRole) before calling entityManager.persist(account) should fix it.

EDIT: Unfortunately, if you cannot be sure if the accountRole already exists in the DB, you will have to check it by querying. If it exists - merge, if not - persist. It is indeed a hassle, but I have not yet seen a better workaround.

EDIT2: The entity you pass to the merge method will remain detached - the managed entity will be returned by the merge, so you would need to merge first, then set the reference on the account to the return value of the merge.

Share:
27,931
Jaanus
Author by

Jaanus

Doing C#, Java 50-50. SOreadytohelp

Updated on July 05, 2022

Comments

  • Jaanus
    Jaanus almost 2 years

    I have 2 entities : Account and AccountRole.

    public class Account {
       private AccountRole accountRole;
    
       @ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
       public AccountRole getAccountRole() {
          return accountRole;
       }
    

    .

    public class AccountRole {
        private Collection<Account> accounts = new ArrayList<Account>();
    
        @OneToMany(mappedBy = "accountRole", fetch = FetchType.EAGER)
        public Collection<Account> getAccounts() {
             return accounts;
        }
    

    Problem comes when I take the accountRole from database and try to persist my Account. At this point I just created my account and role already exists in db.

    AccountRole role = accountService.getRoleFromDatabase(AccountRoles.ROLE_USER);
    account.setAccountRole(role);
    
    //setting both ways, as suggested
    public void setAccountRole(AccountRole accountRole) {
        accountRole.addAccount(this);
        this.accountRole = accountRole;
    }
    
    entityManager.persist(account); // finally in my DAO
    

    I read this : JPA/Hibernate: detached entity passed to persist And what I understood, I must set the entities values from both direction, so that what I am doing in my setter.

    Still getting error.

     org.hibernate.PersistentObjectException: detached entity passed to persist: foo.bar.pojo.AccountRole
    
  • Jaanus
    Jaanus over 11 years
    in rare occasions the AccountRole does not exist in database, so when doing em.persist, it persists the account and accountrole, and everything works. now when I find myself in that case with your code, it is trying to add the AccountRole twice to database, first with merge, with succeedes and then with persist also, thus giving me duplicate key value violation error, any suggestions? problem is that, sometimes the role does not exist.
  • Jaanus
    Jaanus over 11 years
    one solution could be that, if Role does not exist, persist the whole damn thing, but if role does exist, merge it first, but that seems like too much of a hassle, doesn't it?
  • kostja
    kostja over 11 years
    @Jaanus - alas, you are correct :) I have edited the answer accordingly.
  • Jaanus
    Jaanus over 11 years
    just tried that, same thing.. entityManager.merge(account.getAccountRole()); entityManager.persist(account); and same error : detached entity passed to persist: ee.sellin.vahvagame.pojo.AccountRole .. oh boy
  • kostja
    kostja over 11 years
    @Jaanus - there is a quirk in JPA with merging - I forgot to mention it, sorry. See second edit.
  • rumman0786
    rumman0786 about 7 years
    Also note that, persist() will make the object you passed a managed entity, however, merge will return a managed copy of the object of you passed. So if you need a managed entity after merge you should do account = entityManager.merge(account) Also take a look here