333

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?

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Paul Sanwald
  • 10,899
  • 6
  • 44
  • 59
  • 26
    In my case, I was setting id of an entity which I was trying to persist using Entity Manager. When, I removed the setter for id, it started working fine. – Rushi Shah Jun 28 '16 at 11:23
  • 3
    In my case, I was not setting the id, but there were two users using the same account, one of them persisted an entity (correctly), and the error ocurred when the second one latter tried to persist the same entity, that was already persisted. – sergioFC Nov 14 '19 at 08:03

24 Answers24

395

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.

Damiano
  • 791
  • 1
  • 8
  • 22
ochiWlad
  • 4,242
  • 2
  • 13
  • 17
  • 10
    Surprisingly that one worked for me too. It makes no sense since CascadeType.ALL includes all other cascade types... WTF? I have spring 4.0.4, spring data jpa 1.8.0 and hibernate 4.X.. Does anyone have any thoughts why ALL doesn't work, but MERGE does? – Vadim Kirilchuk Oct 01 '15 at 11:03
  • 31
    @VadimKirilchuk This worked for me too and it makes total sense. Since Transaction is PERSISTED, it tries to PERSIST Account as well and that doesn't work since Account already is in the db. But with CascadeType.MERGE the Account is automatically merged instead. – Gunslinger Nov 26 '15 at 15:44
  • 6
    This can happen if you do not use transactions. – lanoxx Sep 10 '16 at 08:33
  • 1
    another solution: try not to insert already persisted object :) – Andrii Plotnikov Jan 18 '17 at 12:16
  • 4
    Thank you man. It's not possible to avoid inserting of persisted object, if you have restriction for the reference key to be NOT NULL. So this is the only solution. Thank you again. – makkasi Feb 12 '18 at 08:41
  • Thank you this solves my hib 5.3 error when saving a many to many relationship with additional fields in join table. – tom Dec 20 '18 at 22:16
  • Can someone explain to me why this worked? It worked for me but I could not understand the reasoning. – Rohith May 30 '19 at 20:40
  • This fixed it for me too when using Spring + Vaadin. – fleed Oct 09 '19 at 15:19
  • This also worked for me, but wtf? I'm either too stupid or it doesn't make any sense at all. For reference, I was using both `PERSIST` and `MERGE` but removed the `PERSIST` for it to work. – Yves Calaci Dec 31 '20 at 03:56
  • Thanks! I wish I could also say "this makes total sense", but Hibernate and JPA are so "random" when it comes to onetoone and manytomany, I give up. Just give me something to try, and hey this worked. Thanks. – tom May 08 '21 at 22:02
  • The "Aha" moment. If I could resume this whole page question I'd higlight with this sentence from @Gunslinger: **it tries to PERSIST Account as well and that doesn't work since Account already is in the db** – testing_22 Jul 19 '22 at 23:26
  • just to be clear. the use of `CascadeType.MERGE` is in both of the entities right? – DoubleS Jun 25 '23 at 09:15
153

This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.

As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.

An example setter for the Many side is in this link.

After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.

Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Sym-Sym
  • 3,578
  • 1
  • 29
  • 30
  • 2
    ps: the @Id annotation is the one that hibernate uses to identify the access type. – Diego Plentz Mar 10 '15 at 18:59
  • 2
    The exception is : `detached entity passed to persist` Why improving consistency makes that it works? Ok, consistency was repaired but object is still detached. – Gilgamesz Mar 13 '18 at 20:17
  • @Gilgamesz, Bi-directional consistency is either satisfied or not. It is not a relative scale to improve. The question code has a Many-To-One bi-directional relationship which did not protect against the case when the Child Entity at the Target of the relationship is **detached**. I do see the answer which suggest `CascadeType.merge` had satisfied many people. Honestly, I used it myself provided that I invoke an `entityManager.find(child)` to ensure that it is in a **Managed state** by the Hibernate Runtime before invoking CRUD operations on the Parent Entity. – Sym-Sym Mar 14 '18 at 02:22
  • @Gilgamesz, You can not guarantee all fellow team members will follow the same practice though. Thus, I prefer my approach to protect my JPA mappings against other developers mis-understanding JPA exceptions and hacking a solution causing more damage!!!!! – Sym-Sym Mar 14 '18 at 02:29
  • @Gilgamesz, I forgot to draw your attention to the example code I [linked in my answer.](https://github.com/SomMeri/org.meri.jpa.tutorial/blob/master/src/main/java/org/meri/jpa/relationships/entities/bestpractice/SafePerson.java) . It shows how to safely deal with the hazard of a detached object. – Sym-Sym Mar 14 '18 at 02:41
  • 1
    @Sam, thanks a lot for your explanation. But, I don't understand still. I see that without 'special' setter the bi-directional relationship is not satisified. But, I don't see why object was detached. – Gilgamesz Mar 14 '18 at 09:33
  • "*The question code has a Many-To-One bi-directional relationship which **did not protect against the case when the Child Entity at the Target of the relationship is detached**. "* I don't understand a highlighted part. Could you explain please? – Gilgamesz Mar 14 '18 at 09:34
  • @Gilgamesz Thank you for your question. Here I [attach one link](https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/) as well as (a second link)[https://stackoverflow.com/a/30168342] . Both are authored by the reputed *Vlad Mihalcea*. I am sure your questions will be satisfied. – Sym-Sym Mar 18 '18 at 17:52
  • The second url is broken. – Gilgamesz Mar 18 '18 at 17:58
  • @Gilgamesz [The second link](https://stackoverflow.com/a/30168342). Hope it helps – Sym-Sym Mar 18 '18 at 21:02
  • 152
    Please dont post an answer that answers solely with links. – El Mac May 16 '18 at 07:50
  • 12
    Cannot see how this answer is related to the question at all? – Eugen Labun Jan 19 '19 at 22:35
  • I am going through [An example setter for the Many side](https://github.com/SomMeri/org.meri.jpa.tutorial/tree/master/src/main/java/org/meri/jpa/relationships/entities/bestpractice) especially between SafePerson and [SafeTwitterAccount](https://github.com/SomMeri/org.meri.jpa.tutorial/blob/master/src/main/java/org/meri/jpa/relationships/entities/bestpractice/SafeTwitterAccount.java). From SafeTwitterAccount.addFollower(SafePerson), the calls goes to SafePerson.startFollowingTwitter(account) and then back to SafeTwitterAccount.addFollower(this); Isn't that leads to infinite loop? – Senthil Oct 10 '20 at 12:53
  • After about reading everything, and wondering whats going, this MERGE worked like bulls eye... – melwinpintoe Dec 05 '20 at 18:46
  • poor quality answer. No information is given without following links (which don't offer help readily either). – java-addict301 Jul 23 '21 at 09:57
  • 1
    low quality answer. just providing links to big webpages with no specifics about where exactly to work is pretty useless. – evilfred Nov 28 '22 at 21:59
  • **Voted down!..** Instead of giving a link that contains lots of unnecessary code, I would prefer to post just the related part. – Jack Mar 04 '23 at 21:24
61

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?

Eugen Labun
  • 2,561
  • 1
  • 22
  • 18
  • Just want to add, if your intention is really to save the child along with parent and also delete parent along with child, like person(parent) and address(child) along with addressId autogenerated by DB then before calling save on Person, just make a call to save on address in your transaction method. This way it would be saved along with id also generated by DB. No impact on performance as Hibenate still makes 2 queries, we are just changing the order of queries. – Vikky Aug 30 '19 at 12:13
  • If we can't pass anything then what default value it take for all case. – Dhwanil Patel Feb 06 '20 at 11:11
  • awesome explanation @Eugen Labun. – Maurice Dec 07 '21 at 14:54
35

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

Angad Bansode
  • 825
  • 6
  • 15
  • 2
    Good advice! But only if id is generated. In case it is assigned it is normal to set the id. – Aldian Jul 04 '18 at 09:47
  • this worked for me. Also, you can use `TestEntityManager.persistAndFlush()` in order to make an instance managed and persistent then synchronize the persistence context to the underlying database. Returns the original source entity – Anyul Rivas Aug 21 '18 at 13:52
  • 1
    this worked like charm – anshulkatta Oct 12 '21 at 17:49
35

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);
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Instead of managing, what about if we add the method with @Prepersist and in that method, just set the `this` reference in all child entities? `void prePersist(){ transactions.foreach( t -> t.setAccount(this))` – Faizan Mar 01 '21 at 12:08
  • 1
    @FaizanAhmad That's not going to cover the case when you add the child without adding it to the parent. – Vlad Mihalcea Mar 01 '21 at 13:06
17

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);
}
Zds
  • 4,311
  • 2
  • 24
  • 28
  • I m facing same issue., i have used the `@Transaction(readonly=false)` at the service layer., still i m getting the same issue, – Senthil Arumugam SP Jun 23 '16 at 16:14
  • 1
    I can't say I fully understand why things work this way but placing the persist method and view to Entity mapping together inside an Transactional annotation fixed my issue so thanks. – Deadron Jun 05 '17 at 18:14
  • 1
    This is actually a better solution than the most upvoted one. – Aleksei Maide Oct 01 '18 at 06:26
  • 1
    we shall manage our persistence context to keep our entities in managed state rather than working around to change the entity itself. – Yu Tian Toby Mar 24 '22 at 09:31
14

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.

dan
  • 13,132
  • 3
  • 38
  • 49
  • 3
    it's JPA, so I think the analogous method is .merge(), but that gives me the same exception. To be clear, Transaction is a new object, Account is not. – Paul Sanwald Nov 13 '12 at 23:04
  • @PaulSanwald Using `merge` on `transaction` object you get the same error? – dan Nov 13 '12 at 23:06
  • actually, no, I mis-spoke. if I .merge(transaction), then transaction is not persisted at all. – Paul Sanwald Nov 13 '12 at 23:09
  • @PaulSanwald Hmm, are you sure that `transaction` was not persisted? How did you check. Note that `merge` is returning a reference to the persisted object. – dan Nov 13 '12 at 23:11
  • the object returned by .merge() has a null id. also, I am doing a .findAll() afterwards, and my object isn't there. – Paul Sanwald Nov 13 '12 at 23:18
  • @PaulSanwald Can you try a `flush()` after `merge`? – dan Nov 13 '12 at 23:33
  • I tried this, and get a "Not currently in a transaction" exception. – Paul Sanwald Nov 13 '12 at 23:39
  • @PaulSanwald Are in an active transaction? Can you show/isolate the code, to show the transaction also? – dan Nov 13 '12 at 23:42
  • the entityManager isn't in an active transaction after I call .persist (or .save). should it be? – Paul Sanwald Nov 14 '12 at 13:49
  • @dan my jpa 2.0 `entity manager` does not have a `saveOrUpdate()` method. What version of `entity manager` do I need to get in order to be able to use `saveOrUpdate()`? – CodeMed Nov 10 '14 at 21:35
  • @CodeMed Because the OP was using hibernate, I was suggesting the `saveOrUpdate`, available from the `Session`. If you are using only `jpa` you can use `merge` to `create` or `update` and entity. – dan Nov 10 '14 at 23:08
9

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/

JJ Zabkar
  • 3,792
  • 7
  • 45
  • 65
8

An old question, but came across the same issue recently . Sharing my experience here.

Entity

@Data
@Entity
@Table(name = "COURSE")
public class Course  {

    @Id
    @GeneratedValue
    private Long id;
}

Saving the entity (JUnit)

Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);

Fix

Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);

Conclusion : If the entity class has @GeneratedValue for primary key (id), then ensure that you are not passing a value for the primary key (id)

jfk
  • 4,335
  • 34
  • 27
6

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.

FazoM
  • 4,777
  • 6
  • 43
  • 61
5

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. :)

NemesisX00
  • 2,046
  • 2
  • 13
  • 12
  • 2
    yeah I don't think this is it, all the same, I added @JoinColumn(name = "fromAccount_id", referencedColumnName = "id") and it didn't work :). – Paul Sanwald Nov 13 '12 at 23:19
  • Yeah, I usually don't use a mapping xml file for mapping entities to tables, so I usually assume it's annotation based. But if I had to guess, you're using a hibernate.xml to map entities to tables, right? – NemesisX00 Nov 13 '12 at 23:21
  • no, I'm using spring data JPA, so it's all annotation based. I have a "mappedBy" annotation on the other side: @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount") – Paul Sanwald Nov 13 '12 at 23:22
5

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.

dan
  • 741
  • 9
  • 14
3

Here is my fix.

Below is my Entity. Mark that the id is annotated with @GeneratedValue(strategy = GenerationType.AUTO), which means that the id would be generated by the Hibernate. Don't set it when entity object is created. As that will be auto generated by the Hibernate. Mind you if the entity id field is not marked with @GeneratedValue then not assigning the id a value manually is also a crime, which will be greeted with IdentifierGenerationException: ids for this class must be manually assigned before calling save()

@Entity
@Data
@NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String key;

    @Column
    private String value;

}

And here is my main class.

public class SimpleObjectMain {

    public static void main(String[] args) {

        System.out.println("Hello Hello From SimpleObjectMain");

        SimpleObject simpleObject = new SimpleObject();
        simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
        simpleObject.setKey("Friend");
        simpleObject.setValue("Bani");

        EntityManager entityManager = EntityManagerUtil.getEntityManager();
        entityManager.getTransaction().begin();
        entityManager.persist(simpleObject);
        entityManager.getTransaction().commit();

        List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
        for(SimpleObject simple : simpleObjectList){
            System.out.println(simple);
        }

        entityManager.close();
        
    }
}

When I tried saving that, it was throwing that

PersistentObjectException: detached entity passed to persist.

All I needed to fix was remove that id setting line for the simpleObject in the main method.

John Doe
  • 2,752
  • 5
  • 40
  • 58
1

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;
            }
Yaocl
  • 194
  • 1
  • 12
1

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.
stakahop
  • 921
  • 9
  • 18
1
cascadeType.MERGE,fetch= FetchType.LAZY
James Siva
  • 1,243
  • 1
  • 14
  • 19
  • 1
    Hi James and welcome, you should try and avoid code only answers. Please indicate how this solves the problem stated in the question (and when it is applicable or not, API level etc.). – Maarten Bodewes Aug 23 '18 at 19:22
  • VLQ reviewers: see https://meta.stackoverflow.com/questions/260411/reviewing-low-quality-posts-answers-without-explanation. code-only answers do not merit deletion, the appropriate action is to select "Looks OK". – Nathan Hughes Aug 23 '18 at 21:08
1
@OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})

worked for me.

Pierre
  • 1,162
  • 12
  • 29
1

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

Shahid Hussain Abbasi
  • 2,508
  • 16
  • 10
0

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

H.Ostwal
  • 333
  • 1
  • 5
  • 11
0

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.

Shubham
  • 707
  • 9
  • 7
0

Another reason I have encountered this issue is having Entities that aren't versioned by Hibernate in a transaction.

Add a @Version annotation to all mapped entities

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;

    @Version
    private Integer version;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "orders")
    private CustomerOrders orders;

}
@Entity
public class CustomerOrders {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;

    @Version
    private Integer version;

    private BigDecimal value;

}
tomaytotomato
  • 3,788
  • 16
  • 64
  • 119
0

This error comes from the JPA Lifecycle. To solve, no need to use specific decorator. Just join the entity using merge like that :

entityManager.merge(transaction);

And don't forget to correctly set up your getter and setter so your both side are sync.

MrSolarius
  • 599
  • 11
  • 28
0

So I stumbled across this Question and Answers because I got the same Error but a very basic object with just Strings and Integers.

But in my case I was trying to set a Value to a Field which was annotated with @Id.

So if you are using @Id it seems that you can't create a new Object on a Class and set an Id by yourself and persist it to Database. You should then leave the Id blank. I wasn't aware and maybe this helps anyone else.

Snickbrack
  • 1,253
  • 4
  • 21
  • 56
0

The problem here is lack of control.

When we use the CrudRepository/JPARepository save method we loose the transactional control.

To overcome this issue we have Transaction Management

I prefer the @Transactional mechanism

imports

import javax.transaction.Transactional;

Entire Source Code:

package com.oracle.dto;

import lombok.*;

import javax.persistence.*;
import java.util.Date;
import java.util.List;

@Entity
@Data
@ToString(exclude = {"employee"})
@EqualsAndHashCode(exclude = {"employee"})
public class Project {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
    @SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
    @Setter(AccessLevel.NONE)
    @Column(name = "project_id",updatable = false,nullable = false)
    private Integer pId;
    @Column(name="project_name",nullable = false,updatable = true)
    private String projectName;
    @Column(name="team_size",nullable = true,updatable = true)
    private Integer teamSize;
    @Column(name="start_date")
    private Date startDate;
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name="projectemp_join_table",
        joinColumns = {@JoinColumn(name = "project_id")},
        inverseJoinColumns = {@JoinColumn(name="emp_id")}
    )
    private List<Employee> employees;
}
package com.oracle.dto;

import lombok.*;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@EqualsAndHashCode(exclude = {"projects"})
@ToString(exclude = {"projects"})
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
    @SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
    @Setter(AccessLevel.NONE)
    @Column(name = "emp_id",nullable = false,updatable = false)
    private Integer eId;
    @Column(name="fist_name")
    private String firstName;
    @Column(name="last_name")
    private String lastName;
    @ManyToMany(mappedBy = "employees")
    private List<Project> projects;
}


package com.oracle.repo;

import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}

package com.oracle.repo;

import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProjectRepo extends JpaRepository<Project,Integer> {
}

package com.oracle.services;

import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

@Component
public class DBServices {
    @Autowired
    private ProjectRepo repo;
    @Transactional
    public void performActivity(){

        Project p1 = new Project();
        p1.setProjectName("Bank 2");
        p1.setTeamSize(20);
        p1.setStartDate(new Date(2020, 12, 22));

        Project p2 = new Project();
        p2.setProjectName("Bank 1");
        p2.setTeamSize(21);
        p2.setStartDate(new Date(2020, 12, 22));

        Project p3 = new Project();
        p3.setProjectName("Customs");
        p3.setTeamSize(11);
        p3.setStartDate(new Date(2010, 11, 20));

        Employee e1 = new Employee();
        e1.setFirstName("Pratik");
        e1.setLastName("Gaurav");

        Employee e2 = new Employee();
        e2.setFirstName("Ankita");
        e2.setLastName("Noopur");

        Employee e3 = new Employee();
        e3.setFirstName("Rudra");
        e3.setLastName("Narayan");

        List<Employee> empList1 = new LinkedList<Employee>();
        empList1.add(e2);
        empList1.add(e3);

        List<Employee> empList2 = new LinkedList<Employee>();
        empList2.add(e1);
        empList2.add(e2);

        List<Project> pl1=new LinkedList<Project>();
        pl1.add(p1);
        pl1.add(p2);

        List<Project> pl2=new LinkedList<Project>();
        pl2.add(p2);pl2.add(p3);

        p1.setEmployees(empList1);
        p2.setEmployees(empList2);

        e1.setProjects(pl1);
        e2.setProjects(pl2);

        repo.save(p1);
        repo.save(p2);
        repo.save(p3);

    }
}

Pratik Gaurav
  • 661
  • 7
  • 8