Over the years, Hibernate has become close to the defacto standard in the world of Java database persistence. It is powerful, flexible, and boasts excellent performance. In this article, we look at how Java 5 annotations can be used to simplify your Hibernate code and make coding your persistence layer even easier.

Traditionally, Hibernate relies on external XML files for its configuration: database mappings are defined in a set of XML mapping files and loaded at startup time. There are many ways to create these mappings, either automatically, from an existing database schema or Java class model, or by hand. In any case, you can end up with a considerable number of Hibernate mapping files. Alternatively, you can use tools to generate the mapping files from javadoc-style annotations, though this adds an extra step in your build process.

In recent versions of Hibernate, a new, more elegant approach has emerged, based on Java 5 annotations. Using the new Hibernate Annotations library, you can dispense once and for all with your old mapping files--everything is defined as, you guessed it--annotations directly embedded in your Java classes. It turns out that annotations provide a powerful and flexible way of declaring persistence mappings. They are also well supported in recent Java IDEs, with automatic code completion and syntax highlighting.

Hibernate annotations also support the new EJB 3 persistence specifications. These specifications aim at providing a standardized Java persistence mechanism. While Hibernate 3 also provides a few extensions, you can quite easily stick to the standards and code your Hibernate persistence layer using the EJB 3 programming model.

Now, let's take the Hibernate Annotations through its paces.

Installing Hibernate Annotations

To use Hibernate Annotations, you will need at least Hibernate 3.2, and of course Java 5. You can download both Hibernate 3.2 and the Hibernate Annotations library from the Hibernate web site. In addition to the standard Hibernate JARs and dependencies, you will also need the Hibernate Annotations .jar file (hibernate-annotations.jar) Java Persistence API (lib/ejb3-persistence.jar). If you are using Maven, just add the corresponding dependencies into your POM file, as shown here:

  
    ...
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>3.2.1.ga</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>3.2.0.ga</version>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
      <version>1.0</version>
    </dependency>
    ...

The next step is to obtain a Hibernate session factory. You do this a little differently using Hibernate Annotations, though no earth-shattering modifications are necessary. You simply have to use the AnnotationConfiguration class to set up your session factory:

  
    sessionFactory = new AnnotationConfiguration().buildSessionFactory();

As usual, you need to declare your persistence classes in the Hibernate configuration file (typically hibernate.cfg.xml), though you use the <mapping> element to declare your persistent classes:

  
<!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="com.onjava.modelplanes.domain.PlaneType"/>
            <mapping class="com.onjava.modelplanes.domain.ModelPlane"/>
          </session-factory>
        </hibernate-configuration>

Many Java projects these days use lightweight application frameworks such as Spring. If you are using the Spring framework, you can set up an annotation-based Hibernate session factory easily using the AnnotationSessionFactoryBean class, as shown here:

  <!-- Hibernate session factory -->
  <bean id="sessionFactory" 
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
   <property name="dataSource">
     <ref bean="dataSource"/>
   </property>
   <property name="hibernateProperties">
     <props>
       <prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop>
       <prop key="hibernate.hbm2ddl.auto">create</prop>
       ...
     </props>
   </property>
   <property name="annotatedClasses">
     <list>
       <value>com.onjava.modelplanes.domain.PlaneType</value>
       <value>com.onjava.modelplanes.domain.ModelPlane</value>
       ...
     </list>
   </property>
 </bean>

Our First Persistent Class

Now that we know how to obtain our annotation-backed Hibernate session, let's see what an annotated persistent class looks like.

Annotated persistent classes are ordinary POJOs, just like in any other Hibernate application. Well, almost. You do need to add dependencies to the Java Persistence API (javax.persistence.*), and possibly to the Hibernate Annotations packages (org.hibernate.annotations.*) if you use any Hibernate-specific extensions, but other than that, they are just ordinary POJOs with persistence-related annotations. Here's a simple example:

@Entity
public class ModelPlane {

    private Long id;
    private String name;
    
    @Id
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

As we said, it's quite simple. The @Entity annotation declares the class to be persistent. The @Id annotation lets you indicate which property is the unique identifier for this class. In fact, you can either persist fields (annotating the member variables) or properties (annotating the getter methods. For the rest of this article, we will use property-based annotations. One of the nice things about annotation-based persistence is its heavy use of default values (following the "convention over configuration" mantra). For example, you don't need to declare every property to be persistent--any property will be assumed to be persistent unless you tell it otherwise by using the @Transient annotation. This simplifies your code, and also makes for a lot less typing than with the old XML mapping files.

Generating Primary Keys

One thing Hibernate is good at is automatically generating primary keys. The Hibernate/EBJ 3 annotations also provide a rich support for automatic key generation, allowing a variety of strategies. The following example illustrates a frequently used approach, where Hibernate will decide on an appropriate key generation strategy depending on the underlying database:

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public Long getId() {
        return id;
    }

Customizing Table and Field Mappings

By default, Hibernate will map persistent classes to tables and fields with matching names. For example, the above class would map to a table along the following lines:

 
CREATE TABLE MODELPLANE 
(
    ID long,
    NAME varchar
)

This is fine if you are generating and maintaining the database yourself, and makes your code a lot easier to maintain if you can get away with it. However, it doesn't suit everyone's needs. Some applications need to access external databases, and others may need to confirm to company database naming conventions. If necessary, you can use the @Table and @Column annotations to tailor your persistence mappings, as shown here:

@Entity
@Table(name="T_MODEL_PLANE")
public class ModelPlane {

    private Long id;
    private String name;
    
    @Id
    @Column(name="PLANE_ID")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name="PLANE_NAME") 
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

This would map to the following table:

 
CREATE TABLE T_MODEL_PLANE 
(
    PLANE_ID long,
    PLANE_NAME varchar
)

You can also customize your mappings using other table and column attributes. This lets you specify details such as column length, non-null constraints and so forth. Hibernate supports a large number of attributes for these annotations. Here are just a few:

    ...
    @Column(name="PLANE_ID", length=80, nullable=true)
    public String getName() {
        return name;
    }
    ...

Mapping Relationships

One of the most important, and complex, parts of Java persistence mapping is determining how to map relationships between tables. As elsewhere, Hibernate provides a great deal of flexibility in this area, arguably at the cost of a certain degree of complexity. We will look at a few common cases to get an idea of how this is done using annotations.

One of the most commonly-used relationships are many-to-one relationships. Suppose, in the above example, that each ModelPlane is associated with a PlaneType via a many-to-one relationship (in other words, each model plane is associated with exactly one plane type, although a given plane type can be associated with several model planes). You could map this as follows:

    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
    public PlaneType getPlaneType() {
                    return planeType;
          }

The CascadeType values indicate how Hibernate should handle cascading operations.

Another commonly-used relationship is the opposite of the above: the one-to-many-to-one relationship, also known as a collection. Collections are complex beasts, both in old-style Hibernate mapping and when using annotations, and we'll just be skimming the surface here to give you an idea of hown it's done, For example, in the above example, each PlaneType object may contain a collection of ModelPlanes. You could map this as follows:

  
    @OneToMany(mappedBy="planeType",
                   cascade=CascadeType.ALL, 
                   fetch=FetchType.EAGER)
    @OrderBy("name")
    public List<ModelPlane> getModelPlanes() {
        return modelPlanes;
    }

Named Queries

One of the nice features of Hibernate is the ability to declare named queries within your mapping files. These queries can then be invoked by name from within the code, which lets you centralize queries and avoids having SQL or HQL code scattered throughout the application.

You can do this with annotations too, using the @NamedQueries and @NamedQuery annotations, as shown here:

  
@NamedQueries(
 {         
  @NamedQuery(
    name="planeType.findById",
    query="select p from PlaneType p left join fetch p.modelPlanes where id=:id"
  ),
  @NamedQuery(
    name="planeType.findAll",
    query="select p from PlaneType p" 
  ),
  @NamedQuery(
          name="planeType.delete",
          query="delete from PlaneType where id=:id" 
        )  
 }
)

Once defined, you can call them just as you would any other named query.

Conclusion

Hibernate 3 annotations provides a powerful and elegant API to simplify your Java database persistence code, and we really have only scratched the surface here. You can either choose to stick to the standards, and use the Java Persistence API, or take advantage of the Hibernate-specific extensions, which provide more power and flexibility, at the cost of a certain loss of portability. In any case, by eliminating the need for XML mapping files, using Hibernate annotations allows you to simplify your application maintenance, with the additional advantage of giving you a gentle introduction into the world of EJB 3. Check it out!

Resources


Return to ONJava.com.