Thursday, April 19, 2007

Where is "delete-orphans" ?

A Customer has a shopping cart with OrderItems in it:

public class Customer {
    ...
    @OneToMany(cascade=CascadeType.ALL)
    private Set<OrderItem> cart;
    ...
}


What happens if one removes an OrderItem from the cart ? As I noticed today, the item no longer belongs to the customer 's cart but unfortunately still remains in the database as an orphan record.

Ok, let's set the cascade type to all-delete-orphans (like in hibernate) so that orphans get automagically deleted. Not so easy, though. JPA (JSR-220) does not support the "delete-orphan" feature of hibernate and other persistence providers. What can one do? Either use a hibernate specific annotation (other persistence providers like TopLink have similar features) to declare the cascade as delete-orphan or delete the orphan entity programmaticaly (after removing it from the Customer 's collection).

Personally, I chose the second approach as the first ties the application with the specific persistence provider (hibernate in my case).

Friday, April 13, 2007

ORA-00907: missing right parenthesis

What follows is not new. It is very well known by jboss+hibernate users and a lot of info can be found at jboss and hibernate forums. Anyway, it took me almost four hours to clean up the mess so I think it is suitable for this blog (for those who have not realized yet, this blog is mostly about my troubles in the java world). So here it goes:

I was trying to configure a many-to-many relation between documents and document bodies. Each document has a collection of bodies, where each body represents a different translation, so each body corresponds to a different language. Each body on the other hand can belong to many documents, thus making the relation many-to-many. On top of this configuration, I wanted to access the bodies of a document by their language id so the map-based solution seemed ok:

public class MyDocument {
    ...
    @ManyToMany
    @MapKey(name="langId")
    private Map<Integer, MyDocumentBody> bodies;
    ...
}

The bodies Map is keyed by the integer language id.

Unfortunately, due to this bug, hibernate generates illegal sql for Oracle, when trying to initialize the bodies Map. To be more specific, it uses a table alias in the FROM clause but it puts the "AS" keyword in front e.g.:

select * from TableA as a where blah, blah

The "as" is optional but acceptable by many databases. However it is not acceptable by Oracle, as it is explained in the links mentioned above. As I found here the generation of "as" is due to a bug in annotations and the upgrade to hibernate-annotations 3.3.0.GA is suggested. However, the use of this version requires an upgrade of the hibernate-entity manager to version 3.3.x as the compatibility matrix suggests. My troubles didn't end at this point though, because I was using jboss-4.0.5.GA whose EJB3-JPA support is not compatible with the new hibernate-entitymanager. The upgrade to 4.2.0.CR1 seemed inevitable (this has a better EJB3 support but the hibernate-* jars still needed upgrading). Alas, a java.lang.NoClassDefFoundError: org/hibernate/annotations/common/reflection/ReflectionManager was waiting for me during deployment. Another google search and this finally solved my problem. hibernate-annotations-common.jar, which is not included in jboss by default, was needed in the lib folder. Four hours and a lot of googling later, I had my map-based many-to-many relation.

Thursday, April 12, 2007

Deploying an EJB twice ... :-)

OK. I know that what follows is a terrible mistake. Actually I knew it from the very beginning but I was curious to see what would happen. Here is how it goes: Two web applications were running on jboss for some years on two separate machines in the client 's data center. For ease of management, some time ago, the client requested that the two applications should be deployed on one machine with a single instance of jboss. No problem, I thought at first. After installing jboss 4.0.5 on the new machine and testing the two apps separately, it was about time to put them together. In the past we had some problems with such deployments if the two apps were using common libraries. Jboss documentation suggests that if you have two .ear files that include the same jars, a good practice is to remove the duplicate jars from the ear files and put them in the jboss 's lib folder. Another approach is to set

<attribute name="Isolated">true</attribute>

in the ear-deployer.xml file located in the deploy folder. This means that each ear has its own class loader isolated from other deployments and therefore each ear can have the same jars with the other without conflicts such as ClassCastException, LinkageError, etc.

The problem in our case was that the two web apps were using a unique id generator implemented as a session EJB (let 's call it UniqueIDEJB). This EJB had been developed internally years ago, it was packaged in a jar along with all the necessary deployment descriptors and nobody wanted to mess with it. The first ear that contained the UniqueIDEJB was deployed without problems but when the second app was deployed an error occurred indicating that the UniqueIDEJB was already registered in the jndi under the same name.
No problem, one might think. Just deploy the second instance under a different jndi name. After all, that is what deployment descriptors are for. Remember though that nobody wanted to mess with the legacy EJB, so we chose to remove to jar containing the EJB from the two ear files and deploy it independently.

At first, all went well and the two apps along with the UniqueIDEJB were deployed successfully. However there still was a jar file containing the client stubs of the EJB that was included in both apps. This should not be a problem due to the class loader isolation that we used, but things turned out differently. It seems that when the EJB is deployed, jboss creates some proxies that implement the remote and home interfaces, but when the web app looks the EJB up in jndi and tries to cast the result to the home interface, this home interface is loaded by a different class loader (the one of the web app), therefore a ClassCastException occurres.

Conclusion: Class loader isolation does not help when an EJB is included in several web apps. What one should do is deploy the EJB outside of any ear file and put the client stubs jar in jboss 's lib folder.

Tuesday, April 3, 2007