Friday, July 31, 2009

Dynamic Data Access Objects for JPA

JPA is pretty cool and JPA 2.0 looks even better, in a standardised way JPA has managed to rid us of the pain of CRUD, which; when it's all said and done is still the heart of most enterprise apps.

One thing that isn't very clear is how to implement the Data Access Object pattern in JPA. For one thing EntityManager pretty much takes car of abstracting your data access and that as some notable authors have pointed out you don't necessarily need to create a DAO at all. I agree with this but there are a couple of problems I keep running in to:

  • How to manage the vast numbers of queries that you get when you start doing anything useful in an Enterprise System.
  • Testing is made hard in that EntityManager is the pits to test especially when you start writng queries with many parameter, inevitably you end up with a vast brittle network of mocked objects which don't actually add any value and make my life miserable every time you make a change.
  • Named queries are great but but they really bloat your code for a multitude of "adhoc" queries which just seem overkill for me to put into a NamedQuery.
  • Queries are still strings and until JPA2.0 rolls out we can't avoid this indirection. One of things that the DAO pattern used to provide is a method for a query which normally named the purpose of the query and named its parameters and typed them, which was a real blessing when you had to work on code several years old.
Of course the other side of the coin is that we don't want to bring back a lot of boilerplate code, and end up writing too much custom stuff which re-implements features that JPA already has.

My suggestion therefore is to shamelessly steal ideas from Ruby on Rails albeit in a Java-fied manner.

The idea in question is the concept of a finder method in Rails. The one where you can simply call a class method on your Entity: EntityClass.findByName(theName) and thanks to the magic of Ruby's MOP; Rail's ActiveRecord pattern ORM framework can dynamically generate a sql query to go hunt for records for the entity where the name column is equal to "theName". All this without you having to write a single line of additional code.

Of course by now most readers will be smiling since they know that Java definitely has no Meta class facilities. This is of course very true, We can't replicate the exact flexibility of ActiveRecord but we can get some features by resorting to the closest thing Java gives; none other than the good old Dynamic Proxy.

Lets take the following interface:

package repo;
import java.util.Collection;

public interface AnEntityRepository extends EntityRepository<AnEntity> {

AnEntity findByName(String name);

Collection<AnEntity> findAll();

}
This interface extends an interface called EntityRepository (this is just a little marker interface so that I can hit F4 in Eclipse and I also use the type parameter to link the interface to an Entity) Each method on the interface maps to a specific query we want to run against an Entity class "AnEntity", the first method is a simple findByName which will have JPAQL generated dynamically, the next one is findAll which we map to a Named query.

Here is how we will do it: we will pass this interface to the dynamic proxy framework together with our own code to handle invocations which will do the following: first try map the invoked method name to a named query, if that fails we check if the method name starts with "findBy" we then do some funky string gymnastics on the rest of the method name to attempt to extract properties of the targeted entity and then build some JPAQL from that, then create the query and map the passed method parameters as query parameters to it. If any of the previous steps fail we blow up with a Runtime exception.

First step: The proxy framework requires an invocation handler (java.lang.reflect.InvocationHandler), this class is responsible for handling method dispatches from the proxied interface, mine is called RepoInvocationHandler (Repo being short for Repository):

package repo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class RepoInvocationHandler implements InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

}

}
the invoke method will actually handle all the dispatches coming from the proxy, the parameters indicate the proxy instance that called with method, the actual method being called and the parameters that were passed to the method respectively. The first iteration of this invocation handler will be to build a JPAQL from the simple finder method, note that we pass a class in to the invocation handler, this is the entity we wish to query. Here is the implementation of the RepoInvocationHandler to do precisely that:

package repo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import javax.persistence.EntityManager;
import javax.persistence.Query;

public class RepoInvocationHandler implements InvocationHandler {

Class<?> targetEntity;
EntityManager entityManager;

public RepoInvocationHandler(Class<?> targetEntity, EntityManager entityManager) {
this.targetEntity = targetEntity;
this.entityManager = entityManager;
}



public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
String query = buildQuery(name);
Query q = entityManager.createQuery(query);
int parmIndex = 0;
for (Object arg : args) {
q.setParameter(parmIndex++, arg);
}
//we can add the functionality to return a single result or multiples
//by checking if the return type is a Collection instance.
if (Collections.class.isAssignableFrom(method.getReturnType())) {
return q.getResultList();
} else {
return q.getSingleResult();
}

}

String uncapatalise(String property) {
return property.substring(0,1).toLowerCase()+property.substring(1);
}

String buildQuery(String name) {
if (name.startsWith("findBy")) {
String property = name.substring("findBy".length());
//uncapitalise to adhere to conventions
property = uncapatalise(property);
return "select x from " + targetEntity.getName() + " x where x." + property + " = ?";
}
throw new RuntimeException("oops, looks "+name+" is not a finder method");
}

}
A note: you can make finder methods as complex as you want, this case just takes a single property and does an equals to query (select x from repo.AnEntity x where x.name = ?). The possibilities -however - are endless, Ive implemented this with AND and OR functionality so I can write "findByNameAndSurname(String name, String surname) or "findByNameOrSurname(String name, String surname) with n levels of predicates. however I want to keep the features of the generated finder method simple, if your query becomes to complex it should become a NamedQuery, which then brings us to the next iteration.

The next iteration of the invocation handler adds the ability to utilise NamedQuery functionality in order to handle complex queries, it also allows us to override the standard finder functionality:
package repo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import javax.persistence.EntityManager;
import javax.persistence.Query;

public class RepoInvocationHandler implements InvocationHandler {

Class<?> targetEntity;
EntityManager entityManager;

public RepoInvocationHandler(Class<?> targetEntity, EntityManager entityManager) {
this.targetEntity = targetEntity;
this.entityManager = entityManager;
}


public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();

Query q = null;
//Note that I create a named query in the format EntityClassSimpleName.methodName
//this is because named queries are global not so by prefixing the entity class name
//it mostly eliminates duplicate named queries, it also gives a useful convention.
try {
String namedQuery = targetEntity.getSimpleName()+"."+method.getName();
q = entityManager.createNamedQuery(namedQuery);
} catch (IllegalArgumentException e) {
//yes there are more efficient ways to do this, however this is easiest
}
//no named query so lets build one from the method name
if (q == null) {
String query = buildQuery(name);
q = entityManager.createQuery(query);
}
int parmIndex = 0;
for (Object arg : args) {
q.setParameter(parmIndex++, arg);
}
if (Collections.class.isAssignableFrom(method.getReturnType())) {
return q.getResultList();
} else {
return q.getSingleResult();
}


}

String uncapatalise(String property) {
return property.substring(0,1).toLowerCase()+property.substring(1);
}

String buildQuery(String name) {

if (name.startsWith("findBy")) {
String property = name.substring("findBy".length());
//uncapitalise to adhere to conventions
property = uncapatalise(property);
return "select x from " + targetEntity.getName() + " x where " + property + " = ?";
}
throw new RuntimeException("oops, looks "+name+" is not a finder method");
}

}
For reference purposes I've included what the NamedQuery declaration would look like on AnEntity for the method "findAll":
@NamedQueries( {
@NamedQuery(name="AnEntity.findAll", query="select x from AnEntity x")
})
public class AnEntity implements Serializable{
...
}
And there we have the basic functionality I want to implement, the last thing we need to do is create the proxy, I have a simple factory which takes in the Interface (I've forced all the interfaces to extend the EntityRepository interface, for documentation and typing purposes) as follows:
package repo;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import javax.persistence.EntityManager;

public class EntityRepositoryFactory {


public static <T extends EntityRepository<?>> T createEntityRepository(Class<T> homeRepositoryClass, EntityManager entityManager) {
ParameterizedType type = (ParameterizedType) homeRepositoryClass.getGenericInterfaces()[0];
Class<?> entityType = (Class<?>) type.getActualTypeArguments()[0];
return (T) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[] {homeRepositoryClass},
new RepoInvocationHandler(entityType, entityManager));
}

}

Finally using this interface is as simple as creating it via the factory and then calling the methods:
   AnEntityRepository repo = EntityRepositoryFactory
.createEntityRepository(AnEntityRepository.class, entityManager);
AnEntity entity = repo.findByName("Fred");
Collection<AnEntity> allEntities = repo.findAll();

A few final notes:

  • For testing purposes you simply mock out the interface, either with a mocking framework or an anonymous classes.
  • There's nothing stopping you from creating the factory in Spring if you are using Spring.
  • I wish EJB3.1 had introduced a Factory concept for creating instances of beans much like Spring has this would make life easer in the EJB world.
  • Sooner or later you will need to create pagination, you can add this functionality by using parameter annotations, I was thinking of something like: "findAll(@Start int start,@Max int max)";.

No comments: