Sunday, May 30, 2010

Persistence as Aspect - Playing around with AspectJ and JPA

With logging, persistence is often used as an example where Aspect Oriented solves tangling problem. Instead of writing the persisting code to persist an object, one may consider persistency as an aspect and then write the persistence aspect. 

With ORM, the persistency codes have been already better. Instead of  hardcoding queries, developers work directly with object. That looks promising. But now, have a look at the entity class:

@Entity
public class Flight {
  @Column(name="ID")
  @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;
...
}

As you may see, the persistent code tangles the object model. To avoid this tangling, of course, we can write the mapping and the definition in mapping file. But, I'm not really fan of XML because it is not compiled and it has a tendency to be more verbose.

Furthermore, if applications don't care about id, why should we define ID. I remember that thinking of relation to other class as a primary key is not a good Object Oriented programming practice.

So, why don't write PersistenceAspect as the way to annotate the class to make it persistable, after all, using intertype declaration we can add a member and also the annotations ? That's exactly the idea coming to my mind this week, and I made a try to implement the PersistentAspect. In this article, I use EclipseLink as the ORM.

Let's start now. But before starting, let's write the class that persisting a Flight object.
public class FlightMain {
  public static void main(String args[]) throws ParseException {
    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("jpa");
    EntityManager em = emf.createEntityManager();
    try {
      Flight f1 = 
            createFlight(

               "AF""1204"
               "CDG""201005100800"
               "NCE""201005100910" );
      Flight f2 = 
            createFlight(

                  "AF""1274"
                  "CDG""201005101000"
                  "NCE""201005101110" );
      em.getTransaction().begin();
      em.persist(f1);
      em.persist(f2);
      em.getTransaction().commit();
    catch (Exception e) {
      e.printStackTrace();
    }
    finally {
      em.close();
      emf.close();
    }
  }


  private  static Flight createFlight(
      String airline, 
      String number,
      String dep, 
      String depTime, 
      String arr, 
      String arrTime
      throws ParseException {


    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
    Flight flight = new Flight();
    flight.setAirline(airline);
    flight.setNumber(number);
    flight.setDepartureAirport(dep);
    flight.setDepartureTime(sdf.parse(depTime));
    flight.setArrivalAirport(arr);
    flight.setArrivalTime(sdf.parse(arrTime));

    return flight;
  }
}
To start with, I don't want to have Id in my Flight class. All right, move it to the aspect then.

public aspect PersistentAspect {
  @Id  private int Flight.id; 
..
}
To make the story more interesting, let's make the aspect to be responsible to generate id. To do this, we will advise Flight constructor that sets the id. Something like:


public privileged aspect PersistentAspect {

...
  @Id  private int Flight.id; 
  private int generatedId = 10000;
  pointcut flightCreation(Flight f):

      executionFlight.new()) && this(f);
  after(Flight f: flightCreation(f) {
    f.id = generatedId++;
  }
}



With this aspect definition , we can run FlightMain above. It should work... 
Unfortunately, no. I had error because a colum did not exist. Inspecting the log, I had the following query:

INSERT INTO FLIGHT (
AJC$INTERFIELD$COM_SCATLING_PERSITENCE_PERSISTENTASPECT$ID
ARRIVALTIME, 
DEPARTUREAIRPORT, 
ARRIVALAIRPORT, 
DEPARTURETIME, 
NUMBER, 
AIRLINE)

Of course, EclipseLink works with the woven class of Flight, and the woven class has that AJC$INTERFIELD...$ID instead of ID. Well, then it means that we need to explicitly define the column name for intertype declaration. That's what we're going to do:
public aspect PersistentAspect {

...
  @Column(name="ID")
  @Id  private int Flight.id; 

  private int generatedId = 10000;
  pointcut flightCreation(Flight f)

     executionFlight.new()) && this(f);


  after(Flight f: flightCreation(f) {     f.id = generatedId++;
  }
}

With this modification included now, the FlightMain works fine, two Flight objects are stored in the database.


Not satisfied with extracting id from Flight, I also want to strip @Entity from Flight class and also the temporal type for arrivalTime and departureTime. My Flight class becomes:


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

..
}

And my aspect becomes:
public privileged aspect PersistentAspect {

  @Column(name="ID")
  @Id  private int Flight.id; 
  private int generatedId = 10000;

  pointcut flightCreation(Flight f)

     executionFlight.new()) && this(f);

  after(Flight f: flightCreation(f) {
    f.id = generatedId++;
  }
  /** Make Flight class an Entity 
   
   */

  declare @type : Flight: @Entity;

  /** Declares the departure time as timestamp.
   
   */
  declare @field: Date Flight.departureTime: 

     @Temporal(TemporalType.TIMESTAMP);

  

  /** Declares the arrival time as timestamp.
   *  
   */ 
  declare @field: Date Flight.arrivalTime: 

     @Temporal(TemporalType.TIMESTAMP);
}
And yes, it works !



Conclusion and Last Remarks
In this article, aspect can be used as alternative to XML object mapping. At least, we can reduce the need of XML object mapping.   I foun d the need to explicitly intertyped column with ID is not convenient. Maybe, AspectJ should weave the Column annotation itself. 



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.