[java] PersistentObjectException: detached entity passed to persist thrown by JPA and Hibernate

I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.

Here's a snippet of the code:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;

I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)

So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);

How can I correctly save a Transaction, associated with an already persisted Account object?

This question is related to java hibernate jpa entity persist

The answer is


Don't pass id(pk) to persist method or try save() method instead of persist().


You need to set Transaction for every Account.

foreach(Account account : accounts){
    account.setTransaction(transactionObj);
}

Or it colud be enough (if appropriate) to set ids to null on many side.

// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());

foreach(Account account : accounts){
    account.setId(null);
}

transactionObj.setAccounts(accounts);

// just persist transactionObj using EntityManager merge() method.

@OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) worked for me.


Removing child association cascading

So, you need to remove the @CascadeType.ALL from the @ManyToOne association. Child entities should not cascade to parent associations. Only parent entities should cascade to child entities.

@ManyToOne(fetch= FetchType.LAZY)

Notice that I set the fetch attribute to FetchType.LAZY because eager fetching is very bad for performance.

Setting both sides of the association

Whenever you have a bidirectional association, you need to synchronize both sides using addChild and removeChild methods in the parent entity:

public void addTransaction(Transaction transaction) {
    transcations.add(transaction);
    transaction.setAccount(this);
}

public void removeTransaction(Transaction transaction) {
    transcations.remove(transaction);
    transaction.setAccount(null);
}

In my case I was committing transaction when persist method was used. On changing persist to save method , it got resolved.


Remove cascading from the child entity Transaction, it should be just:

@Entity class Transaction {
    @ManyToOne // no cascading here!
    private Account account;
}

(FetchType.EAGER can be removed as well as it's the default for @ManyToOne)

That's all!

Why? By saying "cascade ALL" on the child entity Transaction you require that every DB operation gets propagated to the parent entity Account. If you then do persist(transaction), persist(account) will be invoked as well.

But only transient (new) entities may be passed to persist (Transaction in this case). The detached (or other non-transient state) ones may not (Account in this case, as it's already in DB).

Therefore you get the exception "detached entity passed to persist". The Account entity is meant! Not the Transaction you call persist on.


You generally don't want to propagate from child to parent. Unfortunately there are many code examples in books (even in good ones) and through the net, which do exactly that. I don't know, why... Perhaps sometimes simply copied over and over without much thinking...

Guess what happens if you call remove(transaction) still having "cascade ALL" in that @ManyToOne? The account (btw, with all other transactions!) will be deleted from the DB as well. But that wasn't your intention, was it?


Even if your annotations are declared correctly to properly manage the one-to-many relationship you may still encounter this precise exception. When adding a new child object, Transaction, to an attached data model you'll need to manage the primary key value - unless you're not supposed to. If you supply a primary key value for a child entity declared as follows before calling persist(T), you'll encounter this exception.

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
....

In this case, the annotations are declaring that the database will manage the generation of the entity's primary key values upon insertion. Providing one yourself (such as through the Id's setter) causes this exception.

Alternatively, but effectively the same, this annotation declaration results in the same exception:

@Entity
public class Transaction {
    @Id
    @org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
    @GeneratedValue(generator="system-uuid")
    private Long id;
....

So, don't set the id value in your application code when it's already being managed.


Probably in this case you obtained your account object using the merge logic, and persist is used to persist new objects and it will complain if the hierarchy is having an already persisted object. You should use saveOrUpdate in such cases, instead of persist.


Resolved by saving dependent object before the next.

This was happened to me because I was not setting Id (which was not auto generated). and trying to save with relation @ManytoOne


In your entity definition, you're not specifying the @JoinColumn for the Account joined to a Transaction. You'll want something like this:

@Entity
public class Transaction {
    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    @JoinColumn(name = "accountId", referencedColumnName = "id")
    private Account fromAccount;
}

EDIT: Well, I guess that would be useful if you were using the @Table annotation on your class. Heh. :)


My Spring Data JPA-based answer: I simply added a @Transactional annotation to my outer method.

Why it works

The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.

Reference:

https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/


If above solutions not work just one time comment the getter and setter methods of entity class and do not set the value of id.(Primary key) Then this will work.


cascadeType.MERGE,fetch= FetchType.LAZY

Using merge is risky and tricky, so it's a dirty workaround in your case. You need to remember at least that when you pass an entity object to merge, it stops being attached to the transaction and instead a new, now-attached entity is returned. This means that if anyone has the old entity object still in their possession, changes to it are silently ignored and thrown away on commit.

You are not showing the complete code here, so I cannot double-check your transaction pattern. One way to get to a situation like this is if you don't have a transaction active when executing the merge and persist. In that case persistence provider is expected to open a new transaction for every JPA operation you perform and immediately commit and close it before the call returns. If this is the case, the merge would be run in a first transaction and then after the merge method returns, the transaction is completed and closed and the returned entity is now detached. The persist below it would then open a second transaction, and trying to refer to an entity that is detached, giving an exception. Always wrap your code inside a transaction unless you know very well what you are doing.

Using container-managed transaction it would look something like this. Do note: this assumes the method is inside a session bean and called via Local or Remote interface.

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
    ...

    if (account.getId()!=null) {
        account = entityManager.merge(account);
    }

    Transaction transaction = new Transaction(account,"other stuff");

    entityManager.persist(account);
}

The solution is simple, just use the CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL.

I have had the same problem and CascadeType.MERGE has worked for me.

I hope you are sorted.


Maybe It is OpenJPA's bug, When rollback it reset the @Version field, but the pcVersionInit keep true. I have a AbstraceEntity which declared the @Version field. I can workaround it by reset the pcVersionInit field. But It is not a good idea. I think it not work when have cascade persist entity.

    private static Field PC_VERSION_INIT = null;
    static {
        try {
            PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
            PC_VERSION_INIT.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
        }
    }

    public T call(final EntityManager em) {
                if (PC_VERSION_INIT != null && isDetached(entity)) {
                    try {
                        PC_VERSION_INIT.set(entity, false);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                    }
                }
                em.persist(entity);
                return entity;
            }

            /**
             * @param entity
             * @param detached
             * @return
             */
            private boolean isDetached(final Object entity) {
                if (entity instanceof PersistenceCapable) {
                    PersistenceCapable pc = (PersistenceCapable) entity;
                    if (pc.pcIsDetached() == Boolean.TRUE) {
                        return true;
                    }
                }
                return false;
            }

If nothing helps and you are still getting this exception, review your equals() methods - and don't include child collection in it. Especially if you have deep structure of embedded collections (e.g. A contains Bs, B contains Cs, etc.).

In example of Account -> Transactions:

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }

In above example remove transactions from equals() checks. This is because hibernate will imply that you are not trying to update old object, but you pass a new object to persist, whenever you change element on the child collection.
Of course this solutions will not fit all applications and you should carefully design what you want to include in the equals and hashCode methods.


Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to hibernate

Hibernate Error executing DDL via JDBC Statement How does spring.jpa.hibernate.ddl-auto property exactly work in Spring? Error creating bean with name 'entityManagerFactory' defined in class path resource : Invocation of init method failed JPA Hibernate Persistence exception [PersistenceUnit: default] Unable to build Hibernate SessionFactory Disable all Database related auto configuration in Spring Boot Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] HikariCP - connection is not available Hibernate-sequence doesn't exist How to find distinct rows with field in list using JPA and Spring? Spring Data JPA and Exists query

Examples related to jpa

No converter found capable of converting from type to type How does spring.jpa.hibernate.ddl-auto property exactly work in Spring? Deserialize Java 8 LocalDateTime with JacksonMapper Error creating bean with name 'entityManagerFactory' defined in class path resource : Invocation of init method failed How to beautifully update a JPA entity in Spring Data? JPA Hibernate Persistence exception [PersistenceUnit: default] Unable to build Hibernate SessionFactory How to return a custom object from a Spring Data JPA GROUP BY query How to find distinct rows with field in list using JPA and Spring? What is this spring.jpa.open-in-view=true property in Spring Boot? Spring Data JPA and Exists query

Examples related to entity

org.hibernate.MappingException: Unknown entity: annotations.Users PersistentObjectException: detached entity passed to persist thrown by JPA and Hibernate Improving bulk insert performance in Entity framework What is the difference between persist() and merge() in JPA and Hibernate? How to update only one field using Entity Framework? How to set a default entity property value with Hibernate How to delete an object by id with entity framework How to fix the Hibernate "object references an unsaved transient instance - save the transient instance before flushing" error

Examples related to persist

PersistentObjectException: detached entity passed to persist thrown by JPA and Hibernate JPA EntityManager: Why use persist() over merge()?