Warp 1.0 – beschleunigen!

Dynamic Finders

Die bisher betrachteten Features ähneln in vielfacher Weise denen, wie sie von anderen Frameworks wie Spring oder gar EJB3 bekannt sind. Etwas innovativer hingegen ist ein Ansatz zur Implementierung von Findern. Aufgrund ihrer Natur (JPQL bzw. HQL) sind diese allerdings nur für JPA oder Hibernate sinnvoll. Betrachten wir hier zunächst ein simples Beispiel für einen JPQL-Query (Listing 6).

Listing 6 « Parametrisierter JPA Query »
@Inject
private Provider entityManagerProvider;

public List findByName(String like, int first, int max) {
  EntityManager entityManager = entityManagerProvider.get();
  
  Query query = entityManager.createQuery("SELECT b FROM Brand b WHERE b.name LIKE :likeExpression");
  query = query.setParameter("likeExpression", like);
  query.setFirstResult(first);
  query.setMaxResults(max);
  @SuppressWarnings( { "unchecked", "unchecked" })
  List resultList = query.getResultList();
  return resultList;
}

Die Parametrisierung von Query-Objekten stellt hier eine immer wiederkehrende, manuelle Arbeit dar, die durch Warp-persist vermieden werden kann. Dies wird dadurch erreicht, dass die  Funktionalität dieser Methode vollständig mittels ihrer Signatur und einer zusätzlichen Annotation ausgedrückt wird.

@Finder(query = "SELECT b FROM Brand b WHERE b.name LIKE :likeParameter AND b.owner = :ownerParameter")
public List findByName( 
       @Named("likeParameter") String like, 
       @Named("ownerParameter") String owner, 
       @FirstResult int first, 
       @MaxResults int max)

Hier sind bereits alle notwendigen Informationen enthalten, um den Query zu erzeugen und zu parametrisieren. Query-Parameter werden mithilfe der Named-Annotation einzelnen Methodenparametern zugeordnet, und die Methodenparameter für Offset und Limit sind mit jeweils eigenen Annotations ausgezeichnet. Warp-persist kann hieraus also die Implementierung der Methode zur Laufzeit erstellen. Selbstverständlich sind die Annotations @FirstResult und @MaxResult optional.
Ein weiteres Feature liegt darin, dass Warp-persist anhand der Signatur erkennt, ob für die Abfrage getResultList() oder getSingleResult() verwendet werden muss, sodass lediglich der Rückgabetyp geändert werden muss, um einen eindeutigen Finder zu erhalten.

@Finder(query = "SELECT b FROM Brand b WHERE b.id = :id")
public Brand findById( @Named() int id); // Rückgabetyp signalisiert Einzelergebnis

Wird obiger Code nun in einer Klasse verwendet, ist natürlich eine Dummy-Implementierung der Methode notwendig, um die Klasse übersetzen zu können. Der Quellcode ist dann allerdings eher irreführend, da die eigentliche Implementierung zur Laufzeit von Warp-persist erzeugt wird und die vorliegende nicht aufgerufen wird.

@Finder(query = "SELECT b FROM Brand b WHERE b.name LIKE :likeParameter AND b.owner = :ownerParameter")
public List findByName( 
    @Named("likeParameter") String like, 
    @Named("ownerParameter") String owner) {
  return null; // wird nie aufgerufen.
}

Glücklicherweise kann die obige Annotation auch direkt auf einem Interface verwendet werden, sodass die Implementierung durch den Entwickler komplett entfallen kann.

public interface WarpBrandFinder {
  @Finder(query = "SELECT b FROM Brand b WHERE b.name LIKE :likeParameter")
  public List findByName( 
       @Named("likeParameter") String likeExpression)
}

Diese so genannten Accessor Interfaces werden von Warp-persist dann zur Laufzeit implementiert und genau wie jeder andere Dienst verwendet.

@Inject
private WarpBrandFinder brandFinder;

public void listBrandsEndingWithA() {
  List brands = brandFinder.findByName("%A");
  for (final Brand p : brands) {
    System.out.println(p);
  }
}

Um dies zu ermöglichen, müssen Accessor Interfaces beim PersistenceService angemeldet werden (Listing 7).

Listing 7 « Anmeldung eines Accessor-Interfaces »
Module module = new AbstractModule() {
  protected void configure() {
    bindConstant().annotatedWith(JpaUnit.class).to("example");
    TransactionStrategyBuilder tsb =     PersistenceService.usingJpa().across(UnitOfWork.TRANSACTION);
    tsb.addAccessor(WarpBrandFinder.class); // Anmeldung des Accessors
    PersistenceModuleBuilder pmb =     tsb.forAll(Matchers.annotatedWith(Transactional.class), Matchers.any());
    install(pmb.buildModule());
    bind(PersistenceServiceInitializer.class).asEagerSingleton();    
  }
  // ...
}

Selbstverständlich lassen sich die Dynamic Finder nicht nur mit expliziten, sondern auch mit JPA-Named Queries verwenden. In diesem Fall wird einfach der Parameter query durch namedQuery mit dem eindeutigen Namen der Abfrage ersetzt.

@Finder(namedQuery = "myNamedQuery")
public List findWithNamedQuery( @Named("parameter1") String param1);

Dies birgt bekanntermaßen den Vorteil, dass die Queries während des Deployments, und nicht erst bei Aufruf, geprüft werden. Weiterhin kann man über diesen Weg die von JPA bekannten QueryHints verwenden, die von Warp-persist anderweitig noch nicht unterstützt werden.

Fazit

Warp-persist ist ein noch recht junges Framework. Eventuell fehlende Features, z. B. die direkte Unterstützung von Query-Hints oder die Beschränkung auf ressourcenlokale Transaktionen, muss man in Kauf nehmen. Guice-Freunde finden in Warp-persist dennoch bereits jetzt ein einsatzbereites Mittel, um den Applikationscode kompakter und damit wartbarer zu machen. Warp-persist führt keine für den Anwendungscode sichtbaren neuen Abstraktionen ein, sondern basiert lediglich auf wenigen Annotations und zugehörigen Interceptoren. Somit handelt es sich um ein wenig aufdringliches Framework, das notfalls auch schnell wieder durch ein anderes ersetzt werden könnte, ohne strukturelle Änderung am Quellcode notwendig zu machen. Dies hält sowohl den Einarbeitungsaufwand als auch das Risiko für den Anwendungsentwickler gering. Um es mit den Worten von Jean-Luc Picard auszudrücken: „Warp 1 now. Engage!“

Uwe Schäfer arbeitet als CTO bei der THOMAS DAILY GmbH, dem führenden Dienstleister im Bereich Immobilieninformation. Neben der Architektur, Entwicklung und Qualitätssicherung von Java-EE-Anwendungen interessiert er sich für Prozessoptimierung und innovative Open-Source-Frameworks wie Apache Wicket.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.