Saturday, August 04, 2007

 

Java EE 6 Wishlist Continued

One of the things I don't like very much in the current Java EE 5 specification is how the Java Persistence API (JPA) named queries are handled. Actually they're not handled at all, and that's the problem. What I mean by that is that you don't have the strong Java typing as safety net when using JPA named queries. Apparently I'm not the only one sensing this as a bad smell. There are already some solutions to this problem. The one I've been using for some time now is to add a static query factory method to the entity itself for each named query that it is defining. For example:

@Entity
@NamedQueries(@NamedQuery(name="byName", query="FROM MyEntity WHERE name = :name"))
public class MyEntity {
@Id
String name;

// ...

public static Query createQueryWhereName(EntityManager em, String name) {
Query query = em.createNamedQuery("byName");
query.setParameter("name", name);
return query;
}
}

The advantage of this approach is that the producer and the consumer of the named query are as close to each other as possible. Thus when one changes the definition of the named query it's easy to locate where the hell it's being used and also change the code over there. Although this approach works it still does not solve the problem that the returned query object is 'stripped' of any possible type information that the named query could yield. The most elegant solution here I've seen so far is based on the Query Interface (see section 20.1.2) of the upcoming JDBC 4.0 Specification, JSR 221. Using this query object design pattern the previous example looks as follows:

@Entity
@NamedQueries(@NamedQuery(name="byName", query="FROM MyEntity WHERE name = :name"))
public class MyEntity {
@Id
String name;

// ...

public interface QueryInterface {
@QueryMethod("byName");
MyEntity getEntity(@QueryParam("name") String name);
}
}

Then when you want to execute the named query you simply do:

MyEntity.QueryInterface queryObject = QueryObjectFactory.createQueryObject(entityManager, MyEntity.QueryInterface.class);
MyEntity me = queryObject.getEntity("Frank Cornelis");

Basically the query object executes code equivalent with:

Query query = entityManager.createNamedQuery("byName");
query.setParameter("name", "Frank Cornelis");
return (MyEntity) query.getSingleResult();

The QueryObjectFactory I've implemented so far is capable of handling the following additional method annotation constructions:

@QueryMethod("all")
List<MyEntity> listAll();

Here the query object framework simply executes code equivalent with:

Query query = entityManager.createNamedQuery("all");
return query.getResultList();

Find methods can be done as follows:

@QueryMethod(value = "byName", nullable = true)
MyEntity findMyEntity(@QueryParam("name") String name);

Here the query object framework executes code equivalent with:

Query query = entityManager.createNamedQuery("byName");
query.setParameter("name", "the runtime name value");
List result = query.getResultList();
if (result.isEmpty()) { return null; }
return result.get(0);

You can even retrieve the original query itself via:

@QueryMethod("veryStrangeQuery")
Query getVeryStrangeQuery();

Besides this you can also let the QueryObjectFactory support update queries via an @UpdateMethod annotation.

This query object design pattern is really the way to handle JPA named queries. It gives you back the strong Java typing you've always wanted and still lets you use the named queries directly if needed. Hopefully we'll see the QueryObjectFactory as part of the JPA specification on Java EE 6. JSR 313 already lists JPA as to be updated technology, so that's looking good so far.

Credits go to Frederic Simon. Although I must say that I don't like the annotations he's using on his blog JPA NamedQueries and JDBC 4.0 since it doesn't allow for reuse of named query, and of course, to be modest, my implementation of the QueryObjectFactory is much more powerful. :)

A little copycat footnote to some of you who actually read my blog: as you can see, giving credit to the person who came up with the original idea isn't that difficult. You should try it also.

This page is powered by Blogger. Isn't yours?