0

I use this code to made a copy of entity:

EntityClass obj = em.find(...);
em.detach(obj);
obj.setId(null);
obj.setName("New");
em.persist(obj);
em.flush();

So the issue is - if i do a new copy from this created copy, they both points to the last created copy in entity manager cache!

// Call#1 copy method
Entity obj = em.find(Entity.class, 1); // old object, id = 1
em.detach(obj);
obj.setId(null);
em.persist(obj); // created new object with id = 2
em.flush();

// Call#2 copy method
Entity obj2 = em.find(Entity.class, 2); // our copy, id = 2
em.detach(obj2);
obj2.setId(null);
em.persist(obj2); // created new object with id = 3
em.flush();

// Call another method
Entity someObj = em.find(Entity.class, 2); // returns last copy with id=3!
// it's like after persist obj2 (id=2) points 
// to the same memory address as the new copy with id = 3

evictAll() after copy method execution makes it upside down - now id=2 and id=3 both points to the original copy with id=2. I assume this somehow connected with fact that in Java we do not create new object with constructor, and variable stays the same while in database exists two entities.

Jesse Cook
  • 13
  • 4
  • 1
    Does `Entity obj3 = em.merge(obj2)` work for your case? For EclipseLink you can also refer to this [answer](https://stackoverflow.com/a/17746371/8068435) – Ranjeet Jul 13 '17 at 09:48
  • Yes, now i'm using CopyGroup which works fine. I can't use merge() in my case, because after merge() and flush() i'm trying to get new id of just created entity and getting 0 (with persist() and flush() i'm getting actual id). I also must notice, that cleaning cache with `em.getEntityManagerFactory().getCache().evictAll()` not solving the issue. It's just makes it upside down - now i'm getting original object by id=2 and by id=3 (without evict() id=2 and id=3 points to the last copy). This bug works only if i making copy, and then copy of this new object. – Jesse Cook Jul 13 '17 at 11:08
  • Update: merge() successfully creates first copy, but second copy from this new throws exception - `The attribute [id] of class [...] is mapped to a primary key column in the database. Updates are not allowed.` Obviously, this second time merge() "sees" not new object, but the old one and tries to update it instead of create. – Jesse Cook Jul 13 '17 at 11:23
  • Detach does not make a copy of the object, and I would advise you not to use it and instead create your own copy method or EclipseLink's copy functionality. Put another way, anything that referenced your 'detached' entity before detachment will still reference it after, and cause you grief as they do not handle ID changes all that well. Not sure that is the case here. Try re-executing the em.find(Entity.class, 1) after each flush and see what happened, and know that flush multiple times can cause oddities depending on how the managed entities are traversed, and how changes are detected. – Chris Jul 13 '17 at 18:07

1 Answers1

2

The root of the problem is the major clash between you and JPA:

  • JPA does a lot to abstract away the fact that "object identity" is not the same as "objects having the same primary key". 90% of JPA's complexity stems from introducing this model.
  • you are trying to explicitly build your logic based on the very behavior that JPA is trying to abstract away.

So instead of using JPA, you are fighting it.

If you try to follow the suit, you will end up with something brittle and ugly - and you will also a feeling that JPA is somehow failing you (same feeling you get when you try to hammer nails with a screwdriver).

What you need to do to get this to work nicely, is:

  • either throw away EntityManager after every business operation (this is the default behavior in EE web apps - EntityManager only lives for a single transaction and is discarded)
  • if you want to copy an object, just do that: copy an object. Write (or generate) Java code to do this.

You might end up with elegant, testable code that has no explicit cache operations, no flushes, no "merge" and no "detach".

Sorry, I know this is not the type of advice you wanted to hear. if you want to get into the JPA mindset and see the rationale for JPA's behavior, check out classic Fowler's enterprise patterns, especially Unit of Work and Identity Map

fdreger
  • 12,264
  • 1
  • 36
  • 42