Wir bauen ein Chamäleon


Java macht es einem nicht gerade leicht, DSLs zu schreiben und dabei typsicher zu bleiben. Angenommen, wir wollen eine DSL zum Erstellen von SQL-Abfragen in der Form select(„id“).from(„person“).where(„age > 18“).and(„name like ‚Daniel %'“) basteln: Auf der einen Seite wollen wir vermeiden, einen eigenen Typ für jeden Auswertungsschritt zu erstellen, auf der anderen Seite darf natürlich nicht erlaubt sein, das and() direkt hinter dem select() oder from() aufzurufen, oder from() zweimal u.s.w. Eine Technik, die genau das erlaubt, nenne ich „das Chamäleon“: Unser Objekt bleibt immer das gleiche, schmückt sich aber mit verschiedenen Interfaces. Solange der Nutzer artig ist und nicht castet, ist damit die Typsicherheit gesichert. Und nun etwas Butter bei die Fische:

public interface SelectQuery {
  public FromQuery from(String table);
}

public interface FromQuery {
  public WhereQuery where(String condition);
}

public interface WhereQuery {
  public WhereQuery and(String condition);
  public WhereQuery or(String condition);
}

public class Query implements SelectQuery, FromQuery, WhereQuery {
  private String[] columns;
  private String table;
  private String condition;

  private Query(String... columns) {
    this.columns = columns;
  }

  public FromQuery from(String table) {
    this.table = table;
    return this;
  }

  public WhereQuery where(String condition) {
    this.condition = condition;
    return this;
  }

  public WhereQuery and(String condition) {
    this.condition += " and " + condition;
    return this;
  }

  public WhereQuery or(String condition) {
    this.condition += " or " + condition;
    return this;
  }
  
  @Override public String toString() {
    String cols = java.util.Arrays.toString(columns);
    return "SELECT " + cols.substring(1,cols.length()-1) + " FROM " + table + 
           (condition == null ? "" : " WHERE " + condition);
  }

  public static SelectQuery select(String... columns) { 
     return new Query(columns); 
  }
}

Ein kleiner Test:

public class Test {
  public static void main(String[] args) {
    System.out.println(Query.select("id","name","age").from("person").
        where("age >= 18").and("name like 'Daniel %'")
    );  
  }
}

//--> SELECT id, name, age FROM person WHERE age >= 18 and name like 'Daniel %'

Sachen wie Query.select(„id“,“name“,“age“).where(„age >= 18“).from(„person“) kompilieren nicht. Natürlich ist das hier nur ein Spiel-Beispiel, eine „ordentliche“ DSL müsste schon etwas mehr leisten: GROUP, HAVING, ORDER BY, Unterabfragen, typsichere Ausdrücke statt Strings wie „age >= 18″… Und ich denke, dass sich nicht alle diese Kombinationen mit dem „Chamäleon“ lösen lassen. Trotzdem ist es ein nützliches Werkzeug, um Java ein bisschen gemütlicher einzurichten.

In Scala hat man es natürlich leichter, z.B. gibt es dort den SQL-Wrapper DBC, dessen neueste Version jedoch leider auf sich warten lässt. Aber schick aussehen tut es jedenfalls, wie man hier sehen kann.

Advertisements

3 Gedanken zu “Wir bauen ein Chamäleon

  1. Hallo,

    Du hast recht, es ist nicht unbedingt leicht, DSL’s in Java zu schreiben. DSL’s zu verwenden hingegen schon und das ist ja das schöne daran. Ich sehe, du möchtest mit deiner SQL-DSL in eine ähnliche Richtung gehen wie jOOQ: https://sourceforge.net/apps/trac/jooq/wiki/Examples

    jOOQ unterstützt aber nicht nur das Erstellen von einfachen Abfragen, sondern grundsätzlich sämtliche SQL Standards, inklusive UNIONs, verschachtelte SELECTs, alle Arten von JOINs, Aliasing. Ausserdem werden auch nicht-SQL Standards wie UDT’s, Stored Procedures, etc unterstützt.

    Lukas

  2. Nun ja, das sollte ja nur ein einfaches Beispiel für diese Technik sein, aber ich will das Rad nicht unbedingt neu erfinden. Übrigens funktionieren TypedQueries in JPA2 auch ganz ähnlich.

    • Du hast recht, das Rad wurde schon oft neu erfunden. Ich kenne JPA2 wie auch Hibernate sehr gut. Diese grossen Frameworks sind genau der Grund, weswegen ich denke, dass ich mit jOOQ eine Nische entdeckt habe. Darüber habe ich einen Artikel geschrieben:
      http://java.dzone.com/announcements/simple-and-intuitive-approach

      JPA2 hat mit CriteriaQuery (ich denke du meinst das?) eine gute Idee von Hibernate abgekupfert. Nur verstehe ich nicht, weswegen CriteriaQuery immer noch so viel Abstraktion über SQL setzt. Die DSL erinnert gar nicht mehr an SQL. Ausserdem ist es meiner Meinung nach mit CriteriaQuery nicht möglich, ein self-join zu formulieren. Und sehr umständlich, bei Abfragen mit mehreren JOIN’s die richtigen Felder aus allen möglichen Entitäten zu selektieren.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s