Sunday, May 30, 2010

JPA 2.0 with Eclipse Link and HSQLDB

OK, all is in the title. We'll play a little with EclipseLink implementation of JPA 2.0 with HSQLDB as the database.

Setup

Let's start. First, Maven. We need to modify pom.xml to download the jars and resolve dependencies. We need:
  1. Eclipse Link.
  2. JPA 2.0 spec API (we'll get it from geronimo)
for compilation, and HSQL for run-time.

We have then eclipse link and JPA 2.0 dependency definition:
 <dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.0.0</version>
    <scope>compile</scope>
  </dependency>

  <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-jpa_2.0_spec</artifactId>
      <version>1.1</version>
      <type>jar</type>
      <scope>compile</scope>
  </dependency>

And HSQL:
   <dependency>
      <groupId>hsqldb</groupId>
      <artifactId>hsqldb</artifactId>
      <version>1.8.0.10</version>
      <type>jar</type>
  </dependency>

You may need to define the eclipselink repository too:
 <repository>
     <id>EclipseLink Repo</id>
     <url>http://www.eclipse.org/downloads/download.phpr=1&amp;nf=1&amp;file=/rt/eclipselink/maven.repo</url>
  </repository>

Persistence.xml 

Now, we need to write the persistence.xml that configures the database and persistence unit. We give the persistence unit the name "jpa".
<persistence version="1.0"
    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">

    <persistence-unit name="jpa">
       <properties>
            <property name="javax.persistence.jdbc.driver"
                      value="org.hsqldb.jdbcDriver"/>
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:hsqldb:file:\opt\db\testb;shutdown=true"/>
            <property name="javax.persistence.jdbc.user" value="SA"/>
            <property name="javax.persistence.jdbc.password" value=""/>
        </properties>
    </persistence-unit>
</persistence>



The properties are explained below:

 Name          Value      Explanation

 javax.persistence.jdbc.driver      org.hsqldb.jdbcDriver

 The name of JDBC driver. Here, we use embedded HSQL, so we use org.hsqldb.jdbcDriver.

 javax.persistence.jdbc.url      jdbc:hsqldb:file:\opt\db\testb;shutdown=true  The URL to access. Here, we use file \opt\db\testb file, and we use it as embedded database.

 javax.persistence.jdbc.user      SA  The user id to access the database. SA is the default user id for HSQL DB.  

 javax.persistence.jdbc.password            The password for the user SA


Usually, we need to define the list of classes that are persisted by the JPA implementation. We leave it empty for now, since we haven't defined any classes yet.

Then, put the persistence.xml under resources directory (oh yes, don't forget to put it under META-INF):

Persistent class

Good. We're ready to define the class we want to persist. Basically, any Java classes would do. For this article, we take Flight as example. Flight class represents information about a flight, including the usual airline, flight number, departure and arrival airport codes, and departure and arrival times.

Here is the Flight.java code:

package org.arizal.model;
import java.util.Date;


public class Flight {
  private String number;
  private String airline;
  private String departureAirport;
  private String arrivalAirport;
  private Date departureTime;
  private Date arrivalTime;


  public void setNumber(String number) {
    this.number = number;
  }
  public String getAirline() {
    return airline;
  }
  public void setAirline(String airline) {

    this.airline = airline;
  }
  public String getNumber() {
    return number;
  }
  public void setDepartureTime(Date departureTime) {
    this.departureTime = departureTime;
  }
  public Date getDepartureTime() {
    return departureTime;
  }
  public void setArrivalTime(Date arrivalTime) {
    this.arrivalTime = arrivalTime;
  }
  public Date getArrivalTime() {

    return arrivalTime;
  }
  public void setDepartureAirport(String departureAirport) {
    this.departureAirport = departureAirport;
  }
  public String getDepartureAirport() {
    return departureAirport;
  }
  public void setArrivalAirport(String arrivalAirport) {
    this.arrivalAirport = arrivalAirport;
  }
  public String getArrivalAirport() {
    return arrivalAirport;
  }
  @Override
  public String toString() {
    return getAirline() + getNumber() 
    "[" + getDepartureAirport() ";" + getArrivalAirport() +"]" +
    "[" + getDepartureTime() ";" + getArrivalTime() +"]";
  }    
}
To make the class persistable, we annotate the class with @Entity and add an id that will be used as the identifier of the persisted object.

Is that all ? Almost. Just one more detail, because java.util.Date can be used to define date, time, and timestamp, we need to explicitly annotate properties of type date with its use, not surprisingly TemporalType.DATE, TemporalType.TIME, and TemporalType.TIMESTAMP.

For our example, we'll use the latter.



All right, here is the annotated class with id property:

package org.arizal.model;

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Flight {

  @Id private int id;
  private String number;

  private String airline;
  private String departureAirport;
  private String arrivalAirport;

  @Temporal(TemporalType.TIMESTAMP)
  private Date departureTime;

  @Temporal(TemporalType.TIMESTAMP)
  private Date arrivalTime;

  public void setNumber(String number) {
    this.number = number;
  }
  public String getAirline() {
    return airline;
  }
  public void setAirline(String airline) {
    this.airline = airline;
  }
  public String getNumber() {
    return number;
  }
  public void setDepartureTime(Date departureTime) {
    this.departureTime = departureTime;
  }
  public Date getDepartureTime() {
    return departureTime;
  }
  public void setArrivalTime(Date arrivalTime) {
    this.arrivalTime = arrivalTime;
  }
  public Date getArrivalTime() {
    return arrivalTime;
  }
  public void setDepartureAirport(String departureAirport) {
    this.departureAirport = departureAirport;
  }
  public String getDepartureAirport() {
    return departureAirport;
  }
  public void setArrivalAirport(String arrivalAirport) {
    this.arrivalAirport = arrivalAirport;
  }
  public String getArrivalAirport() {
    return arrivalAirport;
  }
  public void setId(int id) {
    this.id = id;
  }
  public int getId() {
    return id;
  }
  @Override
  public String toString() {
    return getAirline() + getNumber() 
    "[" + getDepartureAirport() ";" + getArrivalAirport() +"]" +
    "[" + getDepartureTime() ";" + getArrivalTime() +"]";
  }
}
Good, we have the persistable class. Now, we add this class to the persistence.xml
We have then:
    <persistence-unit name="jpa">
        <class>org.arizal.model.Flight</class>
        <properties>
            <property name="javax.persistence.jdbc.driver"
                      value="org.hsqldb.jdbcDriver"/>
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:hsqldb:file:\opt\db\testb;shutdown=true"/>
            <property name="javax.persistence.jdbc.user" value="SA"/>
            <property name="javax.persistence.jdbc.password" value=""/>
        </properties>


Database
Now we're ready to define the table in the database. We need to run the database admin of HSQL and then create a table using the following SQL command:

CREATE TABLE FLIGHT
(ID INTEGER NOT NULL PRIMARY KEY,
  NUMBER VARCHAR(4) NOT NULL,
  AIRLINE VARCHAR(3) NOT NULL,
  DEPARTUREAIRPORT VARCHAR(3) NOT NULL,
  ARRIVALAIRPORT VARCHAR(3) NOT NULL,
  DEPARTURETIME DATETIME,
  ARRIVALTIME DATETIME)

Good. Close the admin, and we're ready to code.


Code

The heart of the JPA is the class called EntityManager. The class is the one who hides all the relational database complexity from the code. We need then to instantiate it. The instantiation is done as follow:

    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("jpa");
    EntityManager em = emf.createEntityManager();

Where does jpa come from? It's from the persistence unit we defined at persistence.xml.

We need the code to create Flight object as follow:



private static Flight createFlight(
      int id,
      String airline, 
      String number,
      String dep, 
      String depTime, 
      String arr, 
      String arrTime
      throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
    Flight flight = new Flight();
    flight.setId(id);
    flight.setAirline(airline);
    flight.setNumber(number);
    flight.setDepartureAirport(dep);
    flight.setDepartureTime(sdf.parse(depTime));
    flight.setArrivalAirport(arr);
    flight.setArrivalTime(sdf.parse(arrTime));
    return flight;
  }

Nothing special.

Here is the code to persist three new Flights.


  public static void main(String args[]) throws ParseException, SQLException {
    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("jpa");
    EntityManager em = emf.createEntityManager();
    try {
      Flight f1 = 
        createFlight(1"AF""1204""CDG""201005100800""NCE""201005100910" );
      em.getTransaction().begin();
      em.persist(f1);
      em.getTransaction().commit();

      Flight f2 = 
        createFlight(2"AF""1205""CDG""201005100900""NCE""201005101010" );
      em.getTransaction().begin();
      em.persist(f2);
      em.getTransaction().commit();

      Flight f3 = 
        createFlight(3"AF""1206""CDG""201005100950""NCE""201005101110" );
      em.getTransaction().begin();
      em.persist(f3);
      em.getTransaction().commit();
    finally {
      em.close();
      emf.close();
    }
  }

Check now the database:



Great! Now, what about update ? Here's the code to update flight with id = 2 (AF 1205). Let's say we modify the arrival airport to Lyon (LYS).


public static void main(String args[]) throws ParseException, SQLException {
    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("jpa");

    EntityManager em = emf.createEntityManager();
    try {
      em.getTransaction().begin();
      Flight flight = em.find(Flight.class, 2);
      flight.setArrivalAirport("LYS");
      em.getTransaction().commit();
    finally {
      em.close();
      emf.close();
    }
  }
What about getting all Flight instances ? Hmm..., here we need to launch a query. JPA of course supports query: "SELECT f From Flight f" does the business.
Here it is:

public static void main(String args[]) throws ParseException, SQLException {
    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("jpa");


    EntityManager em = emf.createEntityManager();
    try {
      TypedQuery<Flight> query = 
        em.createQuery("SELECT f from Flight f", Flight.class);
      List<Flight> flights = query.getResultList();
      for (Flight f: flights) {
        System.out.println(f);
      }
    finally {
      em.close();
      emf.close();
    }
  }
OK. That's all for JPA. Of course there are lot of details need to be addressed. But, that's not bad as a start. Maybe I'll write on more subjects on this.

No comments: