• Articles
  • /
  • Starting with JPA and Hibernate
 

Starting with JPA and Hibernate

JPA

Object-Relational Mapping (ORM) has been among us for quite some time, but Java has finally standardized ORM into a spec: the Java Persistence API, or JPA for short. It's officially a part of the Java EE EJB3 spec. JPA consolidates what Hibernate and other ORM frameworks have been doing for the last couple of years by adopting that the good stuff and leaving out the rotten eggs.

Many people associate JPA with the the entire EJB3 spec, since it's an integral part of it, and perhaps because of the bad aftertaste EJB2 left behind (still can't get the foul taste out of my mind), people might be reluctant to adopt the new technology. But hear ye, hear ye: JPA is a loose component of EJB3. EJB3's persistence is built upon JPA, but JPA has no dependency towards the rest of the EJB3 spec.

Hibernate3

For those who've been living on Mars, Hibernate is a ORM framework used in a lot of applications. As one can see from the website, it's founding father was JBoss. It has anchored itself into many development teams and became an intrinsic part of an enterprise-level application. For those using it in it's early years, something I dared to do too, Hibernate was not simple. Off course, it is and was extremely powerful when you got it to work.

But Hibernate3 is a huge leap in the right direction. Foremost because it's an JPA implementation (who could've guessed?). Therefor it's use has been extremely simplified for those not so XML-oriented programmers (the days where XDoclet was used to generate those mapping files might be coming to an end after all). Secondly, thanks to the JPA annotations, we finally have an alternative to the XML mapping files. Bigger hurray! You can still use them though, but after using the annotations, you won't probably use them again.

Entity classes

To start of the article, I'll start by showing the two classes I'll be wanting to store in my database. It's a simple example: Items and stock locations (I've worked on POS systems, so ...).

First, the Item class:

package be.doclo.jpatest;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name="item")
public class Item
{
    @Id @GeneratedValue
    private int id = 0;
    
    @Basic
    private String name = "";
    
    @Lob
    private String eanNumber = "";
    
    @ManyToMany(mappedBy="items", cascade = CascadeType.ALL)
    private List<StockLocation> locations = new ArrayList<StockLocation>();

    public Item(String name, String eanNumber)
    {
        this.name = name;
        this.eanNumber = eanNumber;
    } 
     
    public Item()
    {
        this("", "");
    }
    
    public int getId()
    {
        return this.id;
    }
    
    public void setId(int id)
    {
        this.id = id;
    }
    
    public String getName()
    {
        return this.name;
    }
    
    public void setName(String name)
    {
        this.name = name;
    }

    public List<StockLocation> getLocations()
    {
        return this.locations;
    }
  
    public void setLocations(List<StockLocation> locations)
    {
        this.locations = locations;
    }

    public String getEanNumber()
    {
        return this.eanNumber;
    }
  
    public void setEanNumber(String eanNumber)
    {
        this.eanNumber = eanNumber;
    }

    @Override
    public String toString()
    {
        return getId() + " " + getName();
    }
    
    @Override
    public boolean equals(Object obj) {
        if(obj == null || !(obj instanceof Item) )
        {
            return false;
        }
        else
        {
            return id == ((Item) obj).getId();
        }
    }
}

And second the StockLocation class:

package be.doclo.jpatest;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name="stocklocation")
public class StockLocation
{
    @Id @GeneratedValue
    private int id = 0;
    
    @Basic
    private String name = "";
    
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(
            name="stocklocation_item",
            joinColumns=
                @JoinColumn(name="stocklocation_id", referencedColumnName="id"),
            inverseJoinColumns=
                @JoinColumn(name="item_id", referencedColumnName="id")
        )
    private List<Item> items = new ArrayList<Item>();

    public StockLocation()
    {
        items = new ArrayList<Item>();
    }

    public StockLocation(String name)
    {
        this.name = name;
    }
    
    public int getId()
    {
        return this.id;
    }

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

    public List<Item> getItems()
    {
        return this.items;
    }
 
    public void setItems(List<Item> items)
    {
        this.items = items;
    }

    
    public String getName()
    {
        return this.name;
    }

    public void setName(String name)
    {
        this.name = name;
    }
    
    @Override
    public String toString()
    {
        return getId() + " " + getName();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null || !(obj instanceof StockLocation) )
        {
            return false;
        }
        else
        {
            return id == ((StockLocation) obj).getId();
        }
    }
}

Okay, time to do some explaining.

Persistent classes

Persistent classes in JPA are annotated with the @Entity annotation. It marks that the class can be used in conjunction with JPA.

Additionally, you can mention the table in which the instances of this class need to be stored. This is done through the @Table annotation. One of the properties of this annotation is name, which defines the table name. By default, it uses the classname as the table's name.

Class identity

In JPA, all classes need to have a unique identifier (otherwise it can't retrieve object from the database). See it as the primary key of a table. In JPA's case, you need to annotate one or more fields with the @Id annotation.

Additionally you can use the @GeneratedValue annotation to mark that the value in this field is generated by the database, for example by a sequence (as in Oracle) or an auto-increment field (as in MySQL). Simple properties

With simple properties I mean all primitives (and their respective wrapper classes) with the addition of String. All simple properties are automatically mapped (unless they are annotated with the @Transient annotation). You can annotate them using @Basic (as above), but in it's pure form, you can omit it (as in the examples). However, @Basic has for example a property for eager/lazy fetching, with which you can control the eager fetch of a field. By default, all simple properties are eager-fetched. However... Hibernate CAN handle lazy-fetching of simple properties, but requires that you instrument your classes (which means it needs to add some bytecode). Without that instrumentation, it'll still eager-fetch all simple properties, regardless of their lazy-fetch annotation. Instrumenting classes is beyond the scope of this article.

All properties need to be accompanied by their respective getter and setter (just as you would do in any other POJO) and a class needs to have a default, no-arg constructor (or no contructors at all, since Java will generate a no-arg constructor in that case).

As an alternative to the field annotation, you can also annotate the getters of fields. This is also recommended, but I don't use it :). Nowadays IDE's generate getters and setters for you. By annotating the fields, instead of the getter, this saves me some extra work. And it keeps the JPA annotation at one spot: my field declaration.

Relations

In the example and Item can have 0 or more StockLocations, and a StockLocation can have 0 or more Items. We see a typical many-to-many relationship. In JPA this is done using the @ManyToMany annotation. There are two parts in this annotation (one at each side of the relationship). One of them needs to define how the relationship will be mapped to the database. Here, StockLocation's items property defines the relationship:

@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(
       name="stocklocation_item",
       joinColumns=
           @JoinColumn(name="stocklocation_id", referencedColumnName="id"),
       inverseJoinColumns=
           @JoinColumn(name="item_id", referencedColumnName="id")
)
private List<Item> items = new ArrayList<Item>();

To keep things simple, I'll explain what this will DO. It'll create a table stocklocation_item with 2 fields. The first one, stocklocation_id is a foreign key to the field id in stocklocation, StockLocation's table. The second one, item_id is a foreign key to the field id in item, Item's table.

The cascade part means that when you save a StockLocation with Items, it'll not only save it into stocklocation, but also add the Item instances that aren't allready saved into the item table. In all cases, it'll also add the records into stocklocation_item to finalize the relationship.

The other end of the relationship, the property locations in Item has an easier task:

@ManyToMany(mappedBy="items", cascade = CascadeType.ALL)
private List<StockLocation> locations = new ArrayList<StockLocation>();

In it's @ManyToMany annotation it has to define which field in StockLocation defines the relationship, in this case items. Again, the cascade is added.

Creating a JPA-capable DAO class

Spring has some nice features to make a Data Access Object class for JPA (using the JpaDaoSupport class). But I tend to adhere to the JPA spec and design my DAO's accordingly.

First of all, your DAO needs a beating heart. In JPA's case, this is the EntityManager. This controls all access to persistent entity classes, which means getting entities, saving and deleting them.

In any case, my DAO needs:

@PersistenceContext
private EntityManager entityManager;

public void setEntityManager(EntityManager entityManager)
{
    this.entityManager = entityManager
}

Nope, no getter. The EntityManager of your DAO needs to be kept there. Why do I use the @PersistenceContext annotation. Well, first, it's the JPA spec. Ha! Didn't see that one coming, did ya? But this annotation gets extremely important further on.

Now we can add some DAO operations. I'll show two of them. First, I'll show a simple select. JPA uses it's own query language, JPA-QL. It's not so different from SQL, besides the fact you're querying object instead of tables.

public Item getById(Integer id)
{
    try
    {
        return (Item) entityManager.createQuery("select i from Item i where i.id=?").setParameter(1, id)
                .getSingleResult();
    }
    catch (NoResultException noResultException)
    {
        return null;
    }
}

Quite obvious what this method does: it gets an Item by it's id. Next, I'll show the insert method. This one's extremely easy.

public void save(T object)
{
    entityManager.persist(object);
}

Look ma! No insert statements.

For other methods, I refer to the JPA EntityManager javadocs.

Putting it together with some Spring glue

To be able to use those DAO's in Spring, you only need to add the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx.xsd">

  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
  </bean>

  <bean id="itemDao" class="be.doclo.jpatest.JpaItemDao" />
  <bean id="stockLocationDao" class="be.doclo.jpatest.JpaStockLocationDao" />

  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

  <bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <tx:annotation-driven />

</beans>

I'll explain the beans.

entityManagerFactory

This will create EntityManager instances where needed.

*Dao

Well, your DAO classes.

org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor

Aah, the magical wand. Remember the @PersistenceContext I used in the DAO's? Well, this processor will inject a EntityManager (created by entityManagerFactory) in every field that has that annotation. Sweet bliss. transactionManager

Might as well mention the next one here: <tx:annotation-driven />. To use transactions for your database operations, you need to add this. When you add the @Transactional annotation, this will automatically start a transaction before the method and commit the changes afterwards. Remember, you need a transactional-capable database for this to work (it doesn't work with MyISAM tables in MySQL). Forgetting something?

Now we've made our persistent classes, our DAO and we can use them. But how can JPA know it needs to use Hibernate, or more importantly, where the database resides in which the data needs to be stored? Well, in comes persistence.xml. This file needs to be in the META-INF folder under the root of your project. It contains the information JPA needs to set up it's environment.

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
  <persistence-unit name="jpatest" transaction-type="RESOURCE_LOCAL">
    <properties>
      <property name="hibernate.archive.autodetection" value="hbm, class"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
      <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
      <property name="hibernate.connection.url" value="jdbc:mysql://localhost/jpatest"/>
      <property name="hibernate.connection.username" value="root"/>
      <property name="hibernate.use_sql_comments" value="true"/>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
    </properties>
  </persistence-unit>
</persistence>

How does it know it has to use Hibernate. Hehe, it doesn't. It'll search the classpath for a JPA implementation. If you have two (I haven't got a clue why you would do this), you'll need to specify which one you want to use. Consult the JPA spec for this.

In the persistence-unit you'll find the familiar hibernate properties: database-url, driver, username. Nice one is the hbm2ddl.auto property. This'll create the database every time you startup the application (destroying existing data in the process). Never EVER use this in production environments, but it'll quite useful when developing.

That's all folks

I hope you've learned something new and are eager to use JPA. I know I was when I came across it.

 
2009 © Lieven Doclo