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.
Related
Suppose a detached entity foo, its version is 10. Another user changed the Foo, and its version becomes 11 in database.
em.merge(foo)
Some JPA provider(e.g. Hibernate) will load the entity from persistence and copy changes of the detached foo to the entity(managed). The entity of the same version or the newest version will be loaded? Throw Optimistic Lock exception at merge time or at flush time?
According to Vlad Mihalcea's blog:
... to associate a detached entity to an active Hibernate Session, you can choose one of the following options:
Reattaching Hibernate (but not JPA 2.1) supports reattaching through
the Session#update method.
A Hibernate Session can only associate one Entity object for a given
database row. This is because the Persistence Context acts as an
in-memory cache (first level cache) and only one value (entity) is
associated to a given key (entity type and database identifier).
An entity can be reattached only if there is no other JVM object
(matching the same database row) already associated to the current
Hibernate Session.
Merging The merge operaration is going to copy the detached entity
state (source) to a managed entity instance (destination). If the
merging entity has no equivalent in the current Session, one will be
fetched from the database.
The detached object instance will continue to remain detached even after the merge operation.
So if you're in the second case, and Session has no related managed entity, a call will be fired to retrieve a brand new entity. Since read is no transactional, you can't lock anything.
I have a JPA entity that links to others -- something like this:
#Entity
class LinkRec implements Serializable {
...
#OneToOne
private OtherEntity otherTable;
...
}
So my logic eventually can delete this entity (calling the EntityManger.remove method), then I want to write to a log file what was done, including reference members of the otherTable object. Is this a permitted operation in JPA?
Is this a permitted operation in JPA?
Yes.
What JPA (underlying JPA provider) does when you invoke remove is just "mark" that the instance is expected to be deleted/removed. But even if the transaction is committed (and the instance deleted from the database) or not, the instance object remains the same. Any changes on its attributes depend on what you do.
Due to you mark the entity as removed you won't can refresh the instance's state from the database (call EntityManager.refersh method). You will get an IllegalArgumentException.
Be aware that, in other cases, you could screw up if you refresh the entity before loggin what you want.
I quote a text from the JPA specification (see Synchronization to the Database section) that could help you to understand the "JPA" behaivor
Synchronization to the database does not involve a refresh of any managed entities unless the refresh operation is explicitly invoked on those entities or cascaded to them as a result of the specification of the cascade=REFRESH or cascade=ALL annotation element value
The relevant line in the spec is:
After an entity has been removed, its state (except for generated state) will be that of the entity at the point at which the remove operation was called.
Since this is all I can find on the subject in the spec, I would say that it could vary from implementation to implementation. In my opinion, this makes what you are tying to do dangerous. It may work in one JPA implementation and not another, or work in one version and not in an upgrade.
If I had to guess on implementations, I would say that #OneToOne objects will probably work okay. Where I would worry is with things like #OneToMany. In the case of Hibernate for example: this collection may be hydrated and in memory, but it may also point to a proxy. If it is a proxy and you call the getter it will check with the database for the collection and fail to load it because the object is gone.
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.
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
A have a JPA entity that has timestamp field and is distinguished by a complex identifier field. What I need is to update timestamp in an entity that has already been stored, otherwise create and store new entity with the current timestamp.
As it turns out the task is not as simple as it seems from the first sight. The problem is that in concurrent environment I get nasty "Unique index or primary key violation" exception. Here's my code:
// Load existing entity, if any.
Entity e = entityManager.find(Entity.class, id);
if (e == null) {
// Could not find entity with the specified id in the database, so create new one.
e = entityManager.merge(new Entity(id));
}
// Set current time...
e.setTimestamp(new Date());
// ...and finally save entity.
entityManager.flush();
Please note that in this example entity identifier is not generated on insert, it is known in advance.
When two or more of threads run this block of code in parallel, they may simultaneously get null from entityManager.find(Entity.class, id) method call, so they will attempt to save two or more entities at the same time, with the same identifier resulting in error.
I think that there are few solutions to the problem.
Sure I could synchronize this code block with a global lock to prevent concurrent access to the database, but would it be the most efficient way?
Some databases support very handy MERGE statement that updates existing or creates new row if none exists. But I doubt that OpenJPA (JPA implementation of my choice) supports it.
Event if JPA does not support SQL MERGE, I can always fall back to plain old JDBC and do whatever I want with the database. But I don't want to leave comfortable API and mess with hairy JDBC+SQL combination.
There is a magic trick to fix it using standard JPA API only, but I don't know it yet.
Please help.
You are referring to the transaction isolation of JPA transactions. I.e. what is the behaviour of transactions when they access other transactions' resources.
According to this article:
READ_COMMITTED is the expected default Transaction Isolation level for using [..] EJB3 JPA
This means that - yes, you will have problems with the above code.
But JPA doesn't support custom isolation levels.
This thread discusses the topic more extensively. Depending on whether you use Spring or EJB, I think you can make use of the proper transaction strategy.