Search code examples
javajpaeclipselinkclone

JPA: persist after detach (for creating entity copy) confuses EntityManager cache


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.


Solution

  • 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