Wednesday, August 1, 2007

JPA NamedQueries and JDBC 4.0

In one project doing a migration from EJB 2.0 to EJB 3, I found this:
@Entity(name = "Action")
@NamedQueries({
@NamedQuery(name = "Action.findAll", query = "SELECT o FROM Action o"),
@NamedQuery(name = "Action.findByExtCode", query = "SELECT o FROM Action o WHERE o.externalCode = ?1"),
@NamedQuery(name = "Action.findByDescription", query = "SELECT o FROM Action o WHERE o.description = ?1"),
@NamedQuery(name = "Action.findManualActions", query = "SELECT o FROM Action o WHERE o.manual=?1"),
@NamedQuery(name = "Action.findSelectedActions", query = "SELECT o FROM Action o WHERE o.actionType.id <> 3"),
@NamedQuery(name = "Action.findFlowActions", query = "SELECT o FROM Action o WHERE o.actionType.id=3"),
@NamedQuery(name = "Action.findByActionFlowId", query = "SELECT o FROM Action o JOIN o.actionFlows af WHERE af.id = ?1")
})
The code looks like this, because when doing named queries in JPA the name need to be unique for the WHOLE persistence unit. So, we agreed about the naming convention "[entity name].[finder name]" for the name of the query.
It is actually quite nicer an more manageable than EJBQL in xml files, but still there is quite a bunch of copy/paste, String that are not constants, and the usage of named queries is here more problematic than EJB 2.0 home interfaces.
The usage looks like:
Query namedQuery = em.createNamedQuery("Action.findByExtCode");
namedQuery.setParameter(1, "001");
ActionBean actionBean = (ActionBean) namedQuery.getSingleResult();
And this is for only one parameter...

The possible code errors (due to lack of static typing) we get here are:
  1. Errors on the string name for the namedQuery
  2. Errors on the parameter position (the named queries annotation is in the model not close to the business logic executing queries)
  3. Errors in Casting
So, when looking at this, I thought about JDBC 4.0 (jsr 221 chapter 20 of the spec) and finally managed to create a nice dynamic proxy doing the work for JPA.
It is quite clear looking at the code above, a JPA named query can be defined as an interface method. It has:
  • a name (entityName + methodName),
  • a list of parameters (ordered or named),
  • and a result (list or single).
So, with the dynamic proxy the usage code looks like:
ActionQuery actionQuery = NamedQueriesFactory.getQueryProxy(ActionQuery.class, em);
ActionBean action = actionQuery.findByExtCode("001");
And the Queries interface:
@JpaQueriesInterface(prefix = "Action")
public interface ActionQuery {
public Collection<ActionBean> findAll();
public ActionBean findByExtCode(String extCode);
@JpaQueryUseParamNames
public ActionBean findByDescription(@JpaParamName("description")String description);
public Collection<ActionBean> findManualActions(boolean manual);
public Collection<ActionBean> findSelectedActions();
public Collection<ActionBean> findFlowActions();
public Collection<ActionBean> findByFlowActionId(long flowActionId);
}
Which gets all the advantage of strong Java typing.
So, the code of my small running example is here:
http://subversion.jfrog.org/jfrog/greenhouse/jpa/trunk/ and it's using maven of course...
Now, the next step is to use the Annotated Query Interface has NamedQuery provider so it will really look like JDBC 4.0.