Wednesday, July 14, 2010

Playing Around with JPA Criteria API

It's still about multiselect like the one discussed in 005 - JPA Projective Query in EL & Hibernate, but instead of using named query, I will use the Criteria API instead.
Let's use the same query as the other article:
SELECT new com.arizal.flight.FlightNumber" +
          "(f.number, f.airline) " +
           " FROM Flight f WHERE f.departure.code='SYD'")

This can be expressed using Criteria as follow:



public static void main(String args[]) {
    EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("jpa");
    EntityManager em = emf.createEntityManager();
    try {
      CriteriaBuilder cb = em.getCriteriaBuilder();
      CriteriaQuery<FlightNumber> criteriaQ = 

         cb.createQuery(FlightNumber.class);
      Root<Flight> flight = criteriaQ.from(Flight.class);
      criteriaQ.select(
        cb.construct(
            FlightNumber.class, 
            flight.get("number")
            flight.get("airline"))).
      where(cb.equal(flight.get("departure").get("code")"SYD"));
      TypedQuery<FlightNumber> query = em.createQuery(criteriaQ);
      List<FlightNumber> fnList = query.getResultList();

      for (FlightNumber fn : fnList) {
        System.out.println(fn.getAirline() " " + fn.getNumber());
      }
    finally {
      em.close();
      emf.close();
    }
Pretty simple, isn't it ?



Yeah, but let's see closer. Unlike SQL where we do first the selection followed by FROM (e.g. SELECT number, airline FROM flight), CriteriaBuilder imposes the use of FROM first (Root<Flight> flight = criteriaQ.from(Flight.class);) followed by select.
We do then: FROM flight SELECT number, airline. 



Could have been better, don't you think ?



Oh, yes. The code works for both Hibernate and EclipseLink. Hibernate does not suffer from this bug: http://opensource.atlassian.com/projects/hibernate/browse/HHH-5348 

Monday, July 12, 2010

JPA Projective Query in EL & Hibernate

I decided to continue playing around with Hibernate and EclipseLink. I would like to try projective query. This is the query in form SELECT NEW FlightNumber(f.number, f.airline) from Flight f WHERE f.departure.code = 'SYD'.


Here is the data:
 select * from FLIGHT


Here is the FlightNumber class:

public class FlightNumber {
  private String number;
  private String airline;

  public FlightNumber(String number, String airline) {
    this.number = number;
    this.airline = airline;
  }

  public FlightNumber(String number) {
    this.number = number;
  }
  public void setNumber(String number) {
    this.number = number;
  }
  public String getNumber() {
    return number;
  }
  public void setAirline(String airline) {
    this.airline = airline;
  }
  public String getAirline() {
    return airline;
  }
}
which is a view of a Flight class as follow:

public class Flight {
  private String number;
  private String airline;

  @ManyToOne
  @JoinColumn(name="DEPARTURE")
  private Airport departure;


  @ManyToOne
  @JoinColumn(name="ARRIVAL")
  private Airport arrival;


  @Id private int id;


  public String getNumber() {
    return number;
  }


  public void setNumber(String number) {
    this.number = number;
  }


  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public void setDeparture(Airport departure) {
    this.departure = departure;
  }
  public Airport getDeparture() {
    return departure;
  }
  public void setArrival(Airport arrival) {
    this.arrival = arrival;
  }
  public Airport getArrival() {
    return arrival;
  }
  public void setAirline(String airline) {
    this.airline = airline;
  }
  public String getAirline() {
    return airline;
  }
  @Override
  public String toString() {
    return "Flight [number=" + number + ", airline=" + airline
        ", departure=" + departure + ", arrival=" 

        arrival + ", id="+ id + "]";
  }
}
Start with the most basic one, Query embedded in the code:
     Query query = em.createQuery(
          "SELECT new com.arizal.flight.FlightNumber" +
          "(f.number, f.airline) " +
           " FROM Flight f WHERE f.departure.code=:code").
          setParameter("code""SYD");

      List storedFlights = query.getResultList();
      for (Iterator it = storedFlights.iterator(); it.hasNext();) {
        FlightNumber fNumber = (FlightNumber)it.next();
        System.out.println(fNumber.getAirline() " " + fNumber.getNumber());
      }
It works great. But of course named query would be better:
First, the definition (in Flight.java class):
@NamedQuery(name="Flight.findByDeparture"
    query="SELECT new com.arizal.flight.FlightNumber(f.number, f.airline) " +
         "FROM Flight f WHERE f.departure.code=:code")
Then the code:
      Query query = em.createNamedQuery(
          "Flight.findByDeparture").
          setParameter("code""SYD");

      List storedFlights = query.getResultList();
      for (Iterator it = storedFlights.iterator(); it.hasNext();) {
        FlightNumber fNumber = (FlightNumber)it.next();
        System.out.println(fNumber.getAirline() " " + fNumber.getNumber());
      }
It works perfectly. I continue to improve by introducing typed query instead:
      TypedQuery<FlightNumber>query = em.createNamedQuery(
          "Flight.findByDeparture",
          FlightNumber.class).
          setParameter("code""SYD");

      List<FlightNumber> storedFlights = query.getResultList();
      for (FlightNumber fn : storedFlights) {
        System.out.println(fn.getAirline() " " + fn.getNumber());
      }






Unfortunately, I got IllegalArgumentException: Cannot create TypedQuery for query with more than one return. 


Dommage. Changing from named query to embedded one didn't solve the problem.
Changed to EclipseLink 2.0.0 didn't solve the problem either. However, EclipseLink 2.1.0 did solve the problem.

After checking the JIRA Hibernate, the problem is already reported couple days ago:

Saturday, July 10, 2010

Using JPA and Hibernate

So yes. Hibernate is one of the most important actors in JPA specification. Emmanuel Bernard from JBoss presented some new things on Hibernate especially its relationship to JPA on SophiaConf last Friday.  As he said in his tweeter, if you were not there, you've wasted your time. I was there, so I didn't waste my time :-)


OK. That's one thing. Now, let's see how things work with JPA and Hibernate. First, you will need at least 3.5.0 version of Hibernate Entity Manager. The POM looks like the following:


<dependencies>
  ...
   <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>3.5.1-Final</version>
        <scope>compile</scope>
    </dependency>   
 </dependencies>


You may need to define the repository:
 <repositories>
   <repository> 
       <id>hibernate</id>
   <url>https://repository.jboss.org/nexus/content/groups/public</url>
   </repository>
  </repositories>   


OK. 


And, here is my boring Flight class:


package com.arizal.flight;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;


@Entity
public class Flight {
  private String number;
  private String airline;


  @ManyToOne
  @JoinColumn(name="DEPARTURE")
  private Airport departure;


  @ManyToOne
  @JoinColumn(name="ARRIVAL")
  private Airport arrival;

  @Id private int id;
...
}
And Airport:

@Entity
public class Airport {
  @Id private String code;
  private String name;

  public void setCode(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }


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


  public String getName() {
    return name;
  }
}

Then, put the HSQL DB in runtime dependency:
   <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>3.5.1-Final</version>
        <scope>compile</scope>
    </dependency>


This should be enough for us to write the code to persist flight, for example the following:


public static void main(String args[]) {
    try {
      Flight flight = new Flight();
      flight.setId(0);
      flight.setArrival(createAirport("SYD","Sydney"));
      flight.setDeparture(createAirport("MEL""Melbourne"));
      flight.setNumber("123");
      flight.setAirline("QF");

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

      em.getTransaction().begin();
      em.persist(flight);
      em.getTransaction().commit();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
I ran the code, but got this error:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
at org.slf4j.LoggerFactory.getSingleton(LoggerFactory.java:223)
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:120)
at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:111)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:269)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:242)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:255)


Not very cool.  OK, I miss something on slf4j. Add it to the pom.xml as follow solves the problem:
  <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.5.6</version>
        <scope>runtime</scope>
    </dependency>
  
Yes. Now it works.


O, yes. I forgot to tell something. The persistence.xml accepts the JPA properties:

<persistence-unit name="jpa" >  
   <provider>org.hibernate.ejb.HibernatePersistence</provider>  
   <class>com.arizal.flight.Flight</class>
   <class>com.arizal.flight.Airport</class>
   <properties>
            <property name="javax.persistence.jdbc.driver
                      value="org.hsqldb.jdbcDriver"/>
            <property name="javax.persistence.jdbc.url
                      value="jdbc:hsqldb:hsql://localhost/xdb"/>
            <property name="javax.persistence.jdbc.user" value="SA"/>
            <property name="javax.persistence.jdbc.password" value=""/>
             <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
        </properties>
    </persistence-unit>

It was not the case in older version of hibernate entity manager that requires the following properties instead:
    <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
    <property name="hibernate.connection.url" value="jdbc:hsqldb:hsql://localhost/xdb" />
    <property name="hibernate.connection.username" value="sa" />
    <property name="hibernate.connection.password" value="" />

Pretty cool, isn't it ?