I have an in-application cache of some plain simple Entity beans (EJB 3.1, Glassfish, EclipseLink), so that I don't have to look them up in database with findById each time since application needs to be fast. These entity beans are read-only.
So for example entity bean Country or Currency is in the local cache.
Some process in the Java EE application occurs that wishes to update a complex entity bean (i.e. Customer), which uses the simple beans above (Country, Currency...).
What happens then is that, because the connection of simple entity beans to the JPA context is lost, when .merge() is attempted on the Customer bean, JPA wishes to save the simple entity beans as new records in the database, although these exist 100% in the database already, so I guess this is a "detached entity" problem.
Example.
Country country = getFromCacheByName("GB"); // detached entity, but exists in database
Customer customer = getCustomerFromJPA(); // existing JPA attached entity
customer.setCountry(country);
EntityManager.merge(customer); // pseudo code
How to fix the last line, or the bean (Customer) itself, so that it does not try to save the dependant object (Country) on .merge() ?
Thank you.
You should do a find() or merge() on the country before setting it in the Customer. Also, ensure it has the correct Id.
Merge on Customer should also work though, it is odd that it would attempt to insert the country if its Id was existing, but this could depend on how you have configured things. Are you cascading the merge on the country relationship?
Note that EclipseLink has its own cache. So your application cache is probably not needed.
Related
I am learning JPA Entity life cycle and i want to understand the 'Detached' entities. Below is my code,
//Creating a new employee with id and name field
Employee e = new Employee("1001","Sasi");
em.getTransaction().begin();
em.persist(e);
em.getTransaction().commit();
//Detaching Employee from Persistence context
em.detach(e);
//Persisting detached entity
em.getTransaction().begin();
e.setEmployeeId("1002");
em.persist(e);
em.getTransaction().commit();
What i am seeing is, there are two rows inserted in to mysql database instead of getting an error. Could you please let me know why error is not thrown? I am sure that i misunderstood the concept of detached entities and kindly help me in understanding it correctly.
Entities are identified by their IDs not by the object instance. So, if you give the entitiy a new id ("1002" in this case) it's a new entity that can be persisted without errors. You detached an entity with the ID "1001". But that does not affect the entity with ID "1002".
BTW Detaching means you removed the entity from the context, which means the entity manager looses the control over the respective object instance. So, e.g. it can not reload lazy declared OneToMany refrences/lists, a.s.o.
If you have a entity with a new ID, you can persist it. If you have an entity with a already persisted ID you must merge/attache it, then you can persist the attached object, which means you update the persistent entity.
I have a unclear observation, (in web app, detached entities are hard to debug), maybe Eslipelink 2.6 silently save existing detached object twice at new Id (by em.persist() ). No Exception, nothing, silence. Surprise for me.
When I change to update() +persist() all OK.
If entity is deleted or managed it is stored (or its hash) in current EntityManger's persistence context, so JPA know about its state. But how JPA implementation can get to know that given entity is new or detached? Checking if #ID is null will not always work. Is it JPA provider specific?
In other words how JPA know that it need to throw javax.persistence.EntityExistsException during merging?
Here's how Hibernate does it:
if the identifier is generated, use the presence of the identifier
if not, and the entity is versioned (for optimistic locking), use the timestamp or version
if the above is not possible, query the second-level cache or the database to know if the identifier already exists or not.
I am using Datanucleus as the JPA engine to perform CRUD on an entity in Force.com DB. Insert and Select are working fine, but while updating a new row is getting created and delete does not remove the record at all. I am using following for transaction enforcement
Is there kind of an issue with the proxy object to actual object synchronization after the object has been fetched, modified and then subject to updating.
It seems that as the ORM layer (datanucleus+force sdk) is unable to match between the altered object and the original one, it is landing up creating new row.
Any help is highly appreciated.
Thanks
It would help if you can post your code. But I am guessing you might be hitting a known difference in behavior between DataNucleus and other ORMs like Hibernate.
Are you doing something like this?
MyEntity ent = new MyEntity();
ent.setId(idFromWebRequest);
ent.setXXX(valueFromWebRequest);
ent = entityManager.merge(ent);
(where the instantiation and setters might be carried out by a data binding mechanism such as Spring MVC). If you do it like this, it will not work with DataNucleus but it will work with Hibernate. For DataNucleus you must instead do:
MyEntity ent = entityManager.find(MyEntity.class, idFromWebRequest);
ent.setXXX(valueFromWebRequest);
ent = entityManager.merge(ent);
I would prefer it worked like Hibernate, but the DataNucleus team believes this is the correct behavior. Maybe they can chime in. I believe it's a matter of when you consider an entity a new entity vs. a detached entity. If your entity instance is detached, then calling merge on it should reattach it and your database row will be updated at transaction commit / flush. If it's a new instance, then the entity manager will always create a new record.
As for your delete issue, I don't know what it could be. Perhaps you can post a code sample? You can find a complete CRUD sample app using the JPA provider here:
https://github.com/forcedotcom/javasample-musiclib
We are using Toplink implementation of JPA + Spring + EJB. In one of our EJBs we have something like this:
public void updateUser(long userId, String newName){
User u = em.get(User.class, userId);
u.setName(newName);
// no persist is invoked here
}
So, basically this updateUser() method is supposed to update the name of a user with the given userId.
But the author of this method forgot to invoke em.persist(u).
And the strangest thing is that it works fine. How can it be? I was 100% sure that
without invoking em.persist() or em.merge() there is no way that changes could have been saved into database. Could they? Is there any scenario when this could happen?
You're working with a managed entity. If the entity does not become detached because its entity manager is closed, all changes done to the entity are reflected to the database when the session is flushed/closed and the transaction commited.
From the Java EE tutorial:
The state of persistent entities is
synchronized to the database when the
transaction with which the entity is
associated commits.
Edit for clarity and explanation: So there are three distinct modes that an entity could be in during its lifecycle:
Unsaved: The entity has been instantiated, but persist() has not been called yet.
Managed: The entity has been persisted using persist(), or loaded from the database, and is associated with an entity manager session. All changes to the entity are reflected to the database when the entity manager session is flushed.
Detached: The entity's entity manager session was closed. Changes to the entity will not be reflected to the database automatically, but can be merged explicitly using the merge() command.
(How) is it possible to persist a JPA Entity at the databases of multiple servers without copying everything to DTOs?
We have a distributed system. Some applications do have DBs for caching purposes. The JPA Provider throws an Exception in which it complains that it cannot persist a detached object.
But I would like to preserve the ID of the entity with just persisting it in this additional DB.
(JPA 1.2, EJB 3.0, Glassfish v2.1, Toplink Essentials)
Don't em.persist(obj), just em.merge(obj). Merge works with both attached and detached objects.
If you are starting with a detached object, I would merge the object with the respective EntityManagers. If you're trying to keep the identity key the same across the objects, I would pull the key from the first object merged, and use it in the future.
What you probably (I don't know) don't want to do is try and merge an object that is managed by one EM with another EM. You can test this to see if it works, I just don't know what will happen if you try.
So.
YourEntity unattachedEntity = ... // your original entity object.
YourEntity managedEntity = em1.merge(unattachedEntity);
// managedEntity now has the primary key assigned by the DB
unattacheEntity.setPrimaryKey(managedEntity.getPrimaryKey());
em2.merge(unattachedEntity);
em3.merge(unattachedEntity);
Something like that should work ok.