Tuesday, August 26, 2008

Hibernate Inheritance Mapping Strategies

If you've played with Hibernate for a while, one thing you'll really appreciate is how it helps you avoid the "object/relational mapping" paradigm mismatch.

Now, this is great, because it frees you up to focus on getting your object hierarchy / object models right, without (too much) focus on the underlying persistence layer. However, this doesn't come completely for free.

Inheritence is one area where the mistmatch between object-oriented and relational world becomes particularly visible. What this means is that you have to undertake some thought when it comes to mapping classes to tables, in the case where the classes exhibit inheritence.

A number of references (for instances, I read about this in Chapter 3 of the excellent book "Hibernate In Action") talk about the "three different approaches to representing an inheritance hierarchy", as classified in a paper "Mapping Ojects to Relational Databases" by Scott Ambler in 2002. These are:
  • Table per concrete Class ("Table per class") - One table is used for each (non-abstract) class.
  • Table per class hierarchy ("Single table per class hierarchy") - One table is used to map an entire class hierarchy.
  • Table per subclass ("Joined subclasses")- One table is used for every subclass (including abstract classes and interfaces). These only contain columns for the non-inherited properties.
Each of these is mapped (using Hibernate annotations) using the @Inheritance annotations. That is, @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS), @Inheritance(strategy=InheritanceType.SINGLE_TABLE) and @Inheritance(strategy=InheritanceType.JOINED) respectively.

However, recently I found about another way of handling inheritance in my object model being mapped to the persistence layer. It is useful for the case where you have a superclass that does not need to have any corresponding tables in the underlying database, and does not need to be queryable. In this case, a superclass can be annotated with the @MappedSuperclass annotation.

I thought I should mention this, as most of the information about the three strategies for inheritance doesn't seem to mention this as a "fourth" option - probably because in this case the superclass is not actually an entity.

In fact, I had a bit of difficulty understanding the difference between @MappedSuperclass and the @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) strategy. They are, in fact, very similar.

@MappedSuperclass reference states:

"A mapped superclass designates a class whose mapping information is applied to the entities that inherit from it. A mapped superclass has no separate table defined for it."

So, the MappedSuperclass annotation is similar to "table per class" inheritance, but (the difference is) it does not allow querying, persisting or relationships to the superclass. Table per class does allow these (but, if I understand correctly, is fairly inefficient at these compared to the other two inheritance strategies.)

Thus:
  • Mapped superclasses can't be targets of entity relationships
  • They can be either abstract or concrete. (Some references suggest that a mapped superclass normally should be an abstract class).
  • A mapped superclass is not an Entity

More information on these can be found at http://en.wikibooks.org/wiki/Java_Persistence/Inheritance#Mapped_Superclasses

The advantage of using MappedSperclass annotation for me, at this time, is that it seems the simplest approach if I don't want the superclass to be queried or relationships going directly to it. For the top level of my hierarchy this seems to be a good way to go.

Tuesday, August 19, 2008

Changing Web Context-root in MyEclipse

As you would expect, when you create a web application in myEclipse, by default the web context-root is the name of your web application.
(The web context-root defines the URL you'll use to access your web-app).

To change the Web Context-root, right-click on the project and select properties. Then find the MyEclipse->Web tag. Now you can change this property!

You'll then need to re-deploy!

Easy, I know - but it took me a while to find, so why not record it here for posterity...

Problems with the webapp.root system property when using Tomcat & Log4J.

java.lang.IllegalStateException: Web app root system property already set to different value:

I've been developing a web application that uses Log4J for logging. Recently I started a new web app, using essentially the same framework.
(By the way, the framework - which uses Hibernate Annotations and Spring MVC - is based on the one descrbed in this tutorial)

Anyway, in creating this new web app and starting Tomcat, I immediately encountered the following error:

SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.util.Log4jConfigListener
java.lang.IllegalStateException: Web app root system property already set to different value: 'webapp.root' = [MyPath\EclipseProjects\.metadata\.plugins\com.genuitec.eclipse.easie.tomcat.myeclipse\tomcat\webapps\app1Name\] instead of [MyPath\EclipseProjects\.metadata\.plugins\com.genuitec.eclipse.easie.tomcat.myeclipse\tomcat\webapps\app2Name\] - Choose unique values for the 'webAppRootKey' context-param in your web.xml files!
at org.springframework.web.util.WebUtils.setWebAppRootSystemProperty(WebUtils.java:132)
at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:117)


Ok - so why is this??? Well, I'm glad you asked...

First things first, I should note that the L4JWebConfigurer class is:

"A convenience class that performs custom Log4J initialization for web environments, support 2 init parameters..." that are set as context-param in web.xml.

See: http://opensource.objectsbydesign.com/spring-1.1.4/org/springframework/web/util/Log4jWebConfigurer.html
and
http://static.springframework.org/spring/docs/1.2.x/api/org/springframework/web/util/Log4jWebConfigurer.html


However, this convenience class comes with a warning:

"WARNING: Some containers (like Tomcat) do not keep system properties separate per web app. You have to use unique "webAppRootKey" context-params per web app then, to avoid clashes. Other containers like Resin do isolate each web app's system properties: Here you can use the default key (i.e. no "webAppRootKey" context-param at all) without worrying. "

So... given I'm using Tomcat, what does this mean?

It turns out that the webAppRootKey is a Spring context parameter. It exposes the web application's root directory as a system property. So, it is the value of the webAppRootKey that provides the name for the system property to use. That's fine if you've just got one webapp in Tomcat. Spring will just choose a default value for this (app.root) and off you go. However, as the warning above notes, Tomcat does not keep system properties separate per webApp. This means that when we get to the second application, the same system property (app.root) is being set to a new value (our second application's root directory, rather than the first!)

The reason this is occurring at all is that Log4JConfigListener is trying to set the webAppRootKey in both cases. This allows log4J to place log and config files in directories relative to the application root directory, rather than in a fixed path.

Anyway, the real question is how to fix this. One option would be to remove the Log4JConfigListener, or re-configure Log4J so that this doesn't happen - that is by setting 'log4JExposeWebAppRoot' to false. (This is a servlet context parameter that eliminates the use of log file locations relative to the web application's root directory.)

However, in my case, I decided the easiest thing was just to use a non-default name for the webAppRootKey for each of my applications.

That is, I fixed this by placing the following context-parameter into each of the web application's web.xml files:

<context-param>
<param-name>webAppRootKey</param-name>
<param-value>uniqueAppRootNamee</param-value>
</context-param>

This fixed the problem, and everything's working again... Hoorah!

Sunday, August 17, 2008

AVG Free Edition - .bin file is missing

If you're using AVG Anti-Virus 8 (Free edition), you may have just got a message saying that the update failed and a .bin file is missing.

It turns out this is because the update that is trying to occur is, errr, missing. Don't worry, though, it's not your fault. It's a problem with AVG. There are lots of people finding that problem today (16th/17th August 2008), and lots of people wasting time re-installing AVG with no improvement. My advice is wait a 24 hours and see if AVG fix it.

If you're in a real hurry, I can tell you which of the updates is missing and you can then do the other updates manually. To do this:
(1) Start AVG
(2) Click on Update Now.
(3) When the updates are shown in the popup window, click on the link to tell you more.
http://free.avg.com/ww.download-update-appf8
(4) Save the updates to your computer to do a manual update later. The one you want to avoid is (currently) the last one:
http://downloadfree.avg.com/update/u7iavi1616oo.bin - but it isn't there!!!.
(5) use "Tools->Update From Directory" and browse to the other updates to do them manually.

Of course... you could just wait for AVG to fix this. Can't be long now, surely :-)

Monday, August 11, 2008

UnsupportedClassVersionError

I just got the following error when trying to start Tomcat for an Eclipse project:

java.lang.UnsupportedClassVersionError: Bad version number in .class file

It turns out my Tomcat installation seemed to be using an older version of Java to run. To fix this I went into Eclipse Windows->Preferences->Java and made sure that both the "Installed JRE" and "Compiler" tags had the same Java version (Java 6).

I also checked that in the Project preferences under "MyEclipse->Application Servers->Tomcat->MyEclipseTomcat->JDK" that I had the correct JDK selected (6.0.03, in my case).

I also deleted the Java 5 JDK (even though it wasn't selected) and (for good measure) deleted all my .class files... but this was probably a bit of overkill.

Following a complete rebuild it all worked again... Phew!