[JPA] Two providers, two different bahaviours - is switching really a piece of cake?
Joined: Aug 21, 2008
Hi. I am an EJB3 and JPA student, and I developed a simple JavaEE application, which comprises of a web module and an ejb module with jpa inside. I configured two providers, namely TopLink and Hibernate, to use the same set of persistent classes, the very same schema and user and everything. I have an ejb that has two EntityManagers injected, for both the providers mentioned before. The webapp is a simple form that lets me submit a request which contains an attribute saying which provider I want to use (a simple radio group), and underneath is a servlet that has the ejb injected. A method of the ejb calls the appropriate EntityManager, based on the paramter that servlet gives it. Everything runs on Glassfish V2. I did this to be able to quickly test JPA-QL and some other stuff. The thing is, I have noticed something that disturbs me - sometimes the two providers yield different results (one working fine and the other throwing exceptions, most of the time). And they are not very sophisticated tests I am performing. I will give a few of excamples:
1. EntityManager.lock() actually issues a SELECT ... FOR UPDATE when Hibernate is used, while TopLink uses optimistic locking with versioning. I would say this changes quite a bit in the logic of the application. Suppose I am doing some db queries, and also charge the user's credit card - with optimistic locking throwing an exception at the end of the transaction, the credit card might stay charged, if it was done with a remote service of some kind, which doesn't have any notion of distributed transactions or sth. With Hibernate, and its pessimistic locking, the scenario would not be an issue, because noone is able to update the locked row before my transaction commits.
2. Consider such bidirectional one-to-many / many-to-one relationship: a Seller has many Items (I have seen a few books with similar examples (CaveatEmptor, ActionBazaar... the community seems to like them, so here it is ;-)). The Item is the owner of the relationship (JPA requirement), and I don't want it to be optional (there are no orphaned Items). To enforce this, I put a foreign key on ITEM.SELLER_ID to reference SELLER.ID, and set the ITEM.SELLER_ID column to NOT NULL. When I want to remove an Item from the Seller's collection, I invoke this method in Seller:
(I always try to (un)wire both side of the relationship, I do the same when adding an item - I set the Item's seller. Item.setSeller() is package-private so that client code cannot invoke it directly.) Then, I use an EntityManager:
The result: Hibernate works just fine, issuing only one SQL statement (DELETE ... FROM); TopLink throws PersistentException saying that I am violating a database constraint - it sets ITEM.SELLLER_ID to NULL, but as I said earlier it is a NOT NULL column). Suspecting what is happening there, I changed the code a little: Seller class
It sets the seller to another seller that exists to satisfy the foreign key (ITEM.SELLER_ID -> SELLER.ID). And I was right - TopLink issues two statements for this: first it is an UPDATE of the Item entity to change the SELLER_ID, and immediately afterwards is issues a DELETE. So, in the previous case, it was trying to update a row setting NULL (the "new" seller) to ITEM.SELLER_ID, which is prohibited. It turns out Hibernate is smarter in this case, ommiting update statements for entities that are marked for delete anyways. (To fix this, I would not set the Item.seller to null when removing it from the Seller's item collection, but that's not the point.)
3. None of my entity classes is Serializable (does JPA reuqire this?) but despite this I tried to return a detached entity to my webapp to print it on screen. To my surprise, Hibernate didn't even wink at it, and it worked! When tested with TopLink, I got an exception as expected: "java.lang.ClassCastException: model.Item cannot be cast to java.io.Serializable".
I have encountered more of these. Have you? Has anyone actually done a migration from one provider to another? What was it like? Was it as painless as some JPA evangelists say?
Don't get me wrong, I do realize O/RM is quite a complex topic, I really do appreciate having JPA around and in the EJB out of the box. Also, I am just learning this, so maybe I am doing things wrong. But this is what has been bugging me since I started to see the differences.
If you read until now, you are a patient person ;-)