Reputation: 1337
I'm learning JPA. My provider is EclipseLink, and I'm making desktop application, and using application managed EntityManager. In DB, I have table B that references table A, which means that entity class A has list of Bs (and it also has some other lists). Here's simplified situation: I have four ways/scenarios to make changes made by method below visible in calling method and it's EM.
void addB(A a, String desc){
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
mngedA = em.find(A.class, a.getId());
B b = new B();
b.setDesc(desc);
// option 1:
b.setA(mngedA);
em.persist(b);
// option 2:
a.getBList().add(b);
em.getTransaction.commit();
em.refresh(a); //needed in some scenarios
em.close();
}
In calling method I have this:
em = factory.createEntityManager();
A a = em.find(A.class, id);
println(a.getBList().size());
addB(a, "value1");
//em = factory.createEntityManager(); //1
//a = em.find(A.class, id); //2
//em.refresh(a); //3
println(a.getBList().size());
Marked lines could be commented and uncommented, and I figured out 4 combinations that makes second println call print bigger number than first. These are:
Use option 1 in addB (persist new entity directly), use refresh in addB. In calling method use only a = em.find... (comment lines marked with 1 and 3).
Use option 1 in addB, but use refresh only in calling method (uncomment line //3 keep line //2 commented).
Use option 1 in addB but don't use refresh in addB, use refresh in calling method, and "find" A again using new em (uncomment all 3 lines). (almost same as 2. but interesting because of performance)
Use option 2 in addB, don't use refresh at all, uncomment all 3 lines in calling method.
First combination is slowest, it takes most SQL queries. At refresh it loads everything. Second and third combination is in the middle, at refresh it loads only changes from DB. Fourth example is fastest, it doesn't require any additional queries to DB.
Can someone make comment to all of this confusion, how and why this works?
What is the right way to do it and is there a way to achieve effect of fourth combination without creating new EM, but use existing?
If I want to make big and complex desktop application, that maybe has concurrent access do DB, what approach should I have?
Thank you.
Upvotes: 0
Views: 657
Reputation: 21145
If you must use a different EntityManager context, remember that entities loaded outside of it are not apart of it, so changes made to mngedA are not reflected into A unless you force the refresh once the transaction is done. EntityManagers are designed to be used similar to transaction scopes, so seperate EntityManagers are intentionally issolated from each other.
There are any number of solutions for what you want, but if your client is long lived, you might not want to keep a single EM around for its life, as it does contain a cache that will fill up and become stale. Instead, you might want to obtain one for reads as needed, and either clear it when done, or throw it away and obtain new ones as needed. Something like:
A saveAndAddB(A a, String desc){
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
mngedA = em.merge(a);
B b = new B();
b.setDesc(desc);
b.setA(mngedA);
mngedA.getBList().add(b);;
em.persist(b);
em.getTransaction.commit();
em.close();
return mngedA;
}
This will save any changes to A, and return the up-to-date copy from the latest EntityManager. Your application would then keep using this A instance if needed:
em = factory.createEntityManager();
A a = em.find(A.class, id);
em.close();//no longer needed
println(a.getBList().size());
a = addB(a, "value1");
println(a.getBList().size());
An alternative if your process is short lived is to pass the EM into the method, so that all changes can be picked up in the same transaction, but it is more common to pass around detached A instances and merge them into transaction as required. If you don't want to pick up changes to A, you might pass around A's ID to the addB method instead.
Upvotes: 2