Hibernate Annotations

During some prototype work, Peter and I decided to grow our understanding of Hibernate and try out Hibernate Annotations.

At the high-level, there were four things that we needed to do:

  1. Upgrade our version of Hibernate to 3.2.0 CR4, and get the annotations jars
  2. Replace our .hbm.xml files with annotations
  3. Tweak our Hibernate and Spring configurations
  4. Fiddle with our database schema generation utilities

Upgrading Hibernate

At the moment, hibernate.org claims that Hibernate annotations require Hibernate 3.2.0 CR4. As a result, we found ourselves downloading three things:

  1. A new version of JBoss (4.0.4 GA)
  2. The Hibernate Core (3.2.0 CR4)
  3. Hibernate Annotations (3.2.0 CR2) (we would have loved this to be the same number as the core, but sadly, it wasn’t)

The upgrade seemed a bit peculiar to us. For starters, the new version of JBoss has a bundled version of Hibernate Core, but it wasn’t quite the right version (it’s CR2, rather than CR4)

Secondly, one of the common dependencies for Hibernate (two ASM jars, which CGLIB requires) are no longer distributed with JBoss. This confused us a great deal, because we were pretty sure that Hibernate required ASM. It turns out that, new in 3.2.0, Hibernate can be configured to use JBoss’ competitor to CGLIB, called Javassist. CGLIB can’t be fully removed yet, but ASM can.

Although it didn’t seem completely required, we added the following line to our Spring/Hibernate configuration to reflect this new change:

<prop key="hibernate.bytecode.provider">javassist</prop>

The new classpath requirements appear to be:

  • Hibernate 3
    • commons-logging.jar
    • dom4j.jar
    • javassist.jar
    • antlr.jar
    • commons-collections.jar
    • cglib.jar
    • hibernate3.jar
  • Hibernate Annotations
    • hibernate-annotations.jar
    • ejb3-persistence.jar

Replace Our .hbm.xml Files

We only had a couple of persistent classes, so it was easy to perform this conversion. In each case, we found it easy to walk through the classes, adding the appropriate annotations. There were two main “gotchas”:

  1. First, we discovered that, by default, all properties were likely to be mapped; properties that weren’t intended to be persisted to the database needed to be explicitly marked @Transient
  2. There are two @Entity annotations ? one from the javax.persistence package, and one from org.hibernate.annotation. If you used the org.hibernate Entity, you were destined for a world of pain, because Hibernate quietly excluded the class from its list of persistent classes.

Here’s an example:

@Entity
@Table(name="portal_user")
public class User  {

  private Integer id;
  private Organization organization;
  private String userId;
  private String password;
  private String firstName;
  private String lastName;
  private boolean enabled;

  @Id @GeneratedValue(strategy=AUTO) @Column(name="id")
  public Integer getId() {
    return id;
  }
  public void setId(Integer id) {
    this.id = id;
  }

  @Column(name="user_id", nullable=false, length=20, unique=true)
  public String getUserId() {
    return userId;
  }
  public void setUserId(String userId) {
    this.userId = userId;
  }

  @Column(nullable=false, length=50)
  public String getPassword() {
    return password;
  }
  public void setPassword(String password) {
    this.password = password;
  }

  @Column(nullable=false, length=1)
  @Type(type="yes_no")
  public boolean isEnabled() {
    return enabled;
  }
  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }

  ...

  @ManyToOne(optional=false)
  @JoinColumn(name="organization_id", nullable=false)
  @Cascade({CascadeType.SAVE_UPDATE})
  public Organization getOrganization() {
    return organization;
  }
  public void setOrganization(Organization organization) {
    this.organization = organization;
  }
}

A couple of things are worth mentioning:

  1. The “enabled” property used a special Hibernate type, so it got an extra annotation, whereas other properties were more obvious
  2. By default, property names become the column names and the class name becomes the table name. We could override these defaults if we wanted.

Tweaking Hibernate and Spring

Before we started this conversion, we were using only a Spring configuration file to get our Hibernate session factory. We didn’t have a hibernate.cfg.xml file. Unfortunately, Spring doesn’t yet know about Hibernate annotations, so we had to spread our configuration across two files. Here’s our Spring configuration before we started the conversion:

<bean id="org.hibernate.SessionFactory"
   class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="dataSource" ref="javax.sql.DataSource" />
  <property name="mappingResources">
    <list>
      <value>ca/intelliware/example/model/User.hbm.xml</value>
      <value>ca/intelliware/example/model/Organization.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.bytecode.provider">javassist</prop>
      <prop key="hibernate.show_sql">true</prop>
      <prop key="hibernate.cache.use_second_level_cache">false</prop>
      <prop key="hibernate.hbm2ddl.auto">update</prop>
    </props>
  </property>
</bean>

Note the reference to the hbm files.

After conversion, we had two files. First, a hibernate.cfg.xml file:

<!DOCTYPE hibernate-configuration PUBLIC
 "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
  <session-factory>
    <mapping class="ca.intelliware.example.model.Organization"/>
    <mapping class="ca.intelliware.example.model.User"/>
  </session-factory>
</hibernate-configuration>

This file contains only references to the persistent classes. All other Hibernate configuration goes into the Spring file. The important piece looks like this:

<bean id="org.hibernate.SessionFactory"
    class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="dataSource" ref="javax.sql.DataSource" />
  <property name="configLocation">
    <value>classpath:hibernate.cfg.xml</value>
  </property>
  <property  name="configurationClass">
    <value>org.hibernate.cfg.AnnotationConfiguration</value>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.bytecode.provider">javassist</prop>
      <prop key="hibernate.show_sql">true</prop>
      <prop key="hibernate.cache.use_second_level_cache">false</prop>
      <prop key="hibernate.hbm2ddl.auto">update</prop>
    </props>
  </property>
</bean>

Note the reference to the hibernate.cfg.xml file read in from the classpath.

Fiddle with our Database Schema Generation Utilities

This last step is a bit project-specific.

We had created a simple utility to configure and run the Hibernate SchemaUpdate utility, so that we could capture its output, and include that as a revision for our database migrator. Previously, we scraped the project source folders looking for .hbm.xml files; in the new version, we parsed the hibernate.cfg.xml file to get all of our persistent class names.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply