Blog

Erfahren Sie aktuelles aus dem Unternehmen und treten Sie mit unseren Mitarbeitern in Kontakt. 

Einträge

Unenhanced Classes in OpenJPA

Persistence Provider müssen die persistenten Klassen für die Laufzeit um (providerspezifischen) Code erweitern, um bspw. Dirty Checks durchführen zu können oder auch Lazy Loading zu implementieren. Je nach Provider geschieht dieses Enhancement durch Manipulation des Bytecodes transparent zur Laufzeit, durch spezielle Werkzeuge zur Build-Zeit oder durch einen Java-Agenten beim Start der Anwendung.

Teilweise wird auch Subclassing als Verfahren angeboten. Dann leitet der Provider von den persistenten Klassen ab und nutzt diese neuen Klassen anstelle der Originale. Das hat aber diverse Nachteile, von denen die Verfälschung der Laufzeittypen der offensichtlichste ist. Teilweise ist dieses Feature aber auch fehlerhaft implementiert - wie im Falle OpenJPA: Setzt man hier die Property openjpa.RuntimeUnenhancedClasses auf supported und nutzt ansonsten keines der o. a. Enhancement-Verfahren (Build-Zeit oder Agent), so kommt es bei unidirektionalen ManyToMany-Relationen zu NullPointerExceptions. Der Bug ist bekannt (https://issues.apache.org/jira/browse/OPENJPA-1908), wird aber vermutlich nicht gefixt, da Subclassing ohnehin keine gute Idee ist.

Also: Nicht nutzen!

Checkstyle kann nun auch Java 7!

Mein Flehen wurde erhört: Die Version 5.5 ist verfügbar. Sie kennt auch die neuen Syntaxelemente von Java 7. Dazu passend ist für Eclipse eine aktualisierte Version des Plugins cs-eclipse erschienen. Also: Updaten und wieder checken!

Checkstyle mag Java 7 nicht

Ärgerlich, aber derzeit wahr: Checkstyle stolpert über die neuen Sprachfeatures der Version 7. Die Fehlermeldungen treten im Zusammenhang mit dem Diamond Operator und bei Verwendung von Try with Resources auf und lauten in etwa "expecting EOF, found ...". Wann eine neue Version von Checkstyle mit Java-7-Unterstützung da sein wird, ist noch nicht klar. Da kann man derzeit nur abschalten ...

@Resource ist nicht @Resource

Ich bin mal wieder darüber gestolpert, dass sich die Definition von @Resource verändert hat. In Common Annotations 1.0 gab es den Parameter lookup noch nicht, der ist erst mit 1.1 hinzugekommen. Das ist für sich genommen ja nicht schlimm, aber als Maven-Artefakt scheint es nur javax.annotation:jsr250-api:1.0 zu geben, das z. B. auch von javax.enterprise:cdi-api:1.0 referenziert wird. Fakt ist aber, dass die neuere Version schon längst Teil von Java-SE ist und von den aktuellen App-Servern implementiert wird.

Bei Benutzung von javax.annotation:jsr250-api:1.0 ist es von der Build-Konfiguration abhängig, ob man die neue Annotation aus der Standardbibliothek oder die alte aus jsr250-api-1.0.jar zur Verfügung hat. Sollten Sie damit Schwierigkeiten haben, gehen Sie vielleicht so vor wie ich: Nehmen Sie die jsr250-api-1.0.jar, werfen die Annotationen aus dem Paket javax.annotation raus (die sind ja seit Java 6 im Standard) und packen Sie das Ganze als jsr250-api-1.1.jar. Wenn Sie das dann noch in Ihren Maven-Repository-Manager unter javax.annotation:jsr250-api:1.1 laden und in Ihren Maven-Projekten die Version 1.1 im Dependency-Management eintragen, sollte bezüglich @Resource Ruhe im Karton sein.

Sind EJBs böse?

In der letzten Zeit findet man diverse Veröffentlichungen oder auch Vorträge auf Usergroups und Konferenzen, die scheinbar das eine Ziel haben, Enterprise JavaBeans aus der Anwendungsarchitektur zu verbannen. Da werden zunächst EJBs durch entsprechende CDI-Beans ersetzt, weil die ja leichtgewichtiger seien. Aus Entwicklersicht kann ich dem nicht zustimmen: Aus einer einfachen Klasse wird durch eine einzelne Annotation eine EJB. Wenn das schwergewichtig ist ... Natürlich ist auch der Laufzeitaspekt zu berücksichtigen. Und da sind EJBs tatsächlich etwas langsamer als CDI-Beans, aber der Unterschied ist relativ gering - vgl. dazu auch Adam Biens Weblog. Nach der Ersetzung der EJBs stelt man dann fest, dass einige Aspekte fehlen, die EJBs mitbringen, CDI-Beans aber nicht. So z. B. die deklarative Transaktionssteuerung. Die Verfechter des EJB-Abschusses programmieren sich dann mal eben (*) einen CDI-Interceptor mit dem Binding über eine Annotation namens @Transactional o. ä. Analog könnte man für Security, asynchrone Methoden, Timer etc. vorgehen. Ich frage mich nur "warum?". Warum um alles in der Welt sollte ich eine vorhandene, funktionierende Lösung durch eine selbstprogrammierte ersetzen? Als Argument wird gelegentlich angeführt, dass eine Lösung ohne EJBs bspw. in einem Tomcat mit CDI-Container (Weld oder OpenWebbeans) lauffähig wäre. Ja, schon - aber die Lösung mit EJBs ist eben auch in einem Tomcat lauffähig, wenn ich einen entsprechenden Container hinzunehme. Es gibt doch genau diese Plattformen - SIwpas, TomEE, Resin etc., die das nötige Java-EE-6-Webprofil implementieren. Warum sollte ich mich denn selbst kasteien und nur ein Subset davon nutzen? Es muss wohl doch daran liegen, dass EJBs böse sind ... (*) "mal eben" ist in der IT ein Signalwort. Es bedeutet, dass hier höchste Aufmerksamkeit hinsichtlich des zu veranschlagenden Aufwands geboten ist. PS.: Eine Vereinheitlichung der in grossen Teilen konkurrierenden Komponentenkonzepte EJB und CDI ist sicher sinnvoll. Das sollte aber IMHO im Standard (Java EE 7) passieren und nicht mit "Mach ich mal eben selbst"-Schnellschüssen.

Upgrade auf Maven 3

Wir benutzen für unsere Projekte schon seit Langem Maven als Build-Werkzeug. Nachdem die neue Version 3 schon seit ein paar Monaten verfügbar ist, haben wir unsere Projekte nun auch darauf umgestellt. Insgesamt lief der Upgrade problemlos: Nach der Installation (d. h. dem Entpacken) von Maven 3 und dem Setzen der benötigten Pfade konnten die meisten Projekte ohne Änderung gebaut werden. Einige Projekte hatten allerdings Einstellungen im POM-Element reporting, das von Maven 3 ignoriert wird. Diese Einstellungen müssen nun in das Element build integriert werden oder als Konfiguration des Site-Plugins eingetragen werden.
 
Positiv fällt auf, dass ...
- Maven 3 teilweise deutlich schneller geworden ist,
- die Ausgabe besser strukturiert und damit einfacher erfassbar ist,
- die Überprüfung der POMs restriktiver geschieht (fehlende Versionsangaben werden bspw. mit Warnungen belegt).
 
Fazit: Empfehlenswert!

Java EE 6 oder Spring?

Die häufig gestellte Frage, ob Java EE oder Spring eingesetzt werden soll, muss man m. E. auf den vergleichbaren Bereich beider Welten eingrenzen: Es geht hier um die Plattform von Enterprise-Anwendungen, d. h. im Kern um den Dependency Injection Container. Hier hatte J2EE ganz deutliche Schwächen, die in meiner Vorstellung das Spring Framework erst richtig interessant und vorteilhaft gemacht haben. Java EE 6 hat hier aber ganz erheblich nachgelegt und bietet mit CDI einen extrem einfachen und leistungsfähigen Mechanismus zur kontextbasierten Komposition von Anwendungsbausteinen an – und das in nahezu allen Fällen ohne explizite Konfiguration. Die „Schmerzen“, die seinerzeit den Einsatz von Spring nahegelegt haben, sind also nicht mehr vorhanden.

Für den Einsatz von Java EE 6 spricht, dass es abgestimmte und verbindliche Spezifikationen gibt, die eine Stabilität der Anwendungen auch über Versionswechsel hinweg ermöglichen. Im proprietären (= nicht-standardisierten) Bereich ist das nicht so leicht zu garantieren, wie uns bspw. die Historie von Struts vor Augen führt. Sehr positiv finde ich weiterhin die Existenz mehrerer unabhängiger Java-EE-Implementierungen (auch wenn wir für das Full Profile noch ein wenig Geduld aufbringen müssen), wodurch ein Vendor Lock-In im Ansatz abgewehrt wird. In dem Zusammenhang ist uns die (zwar schnell entschärfte) Ankündigung von SpringSource im Herbst 2008, Maintenance Releases kostenpflichtig zu machen, noch gut in Erinnerung.

Die gemachten Überlegungen gelten natürlich nur für Projekte, die noch vor der Auswahl der Plattform stehen - es lohnt nicht, bestehende Software umzubauen. Wenn man mir aber für neue Projekte die Eingangsfrage stellt, entscheide ich mich für die einfache Variante: Java EE 6.

Bean Validation auf Collections

Die zum Java-EE-6-Umfang gehörende Spezifikation Bean Validation (JSR 303) stellt einen wesentlichen Schritt zur einheitlichen Validierung von Geschäftsdaten dar. Wie von einer ersten Version zu erwarten, gibt es allerdings noch Bereiche mit Verbesserungspotenzial.

Mit BV ist es leicht möglich, z. B. ein Feld, das eine Telefonnummer enthalten soll, mit einem entsprechenden Constraint zu belegen:

  @ValidTelNumber
  private String telefonNummer;

Dabei sei angenommen, dass ValidTelNumber eine passend definierte Annotation ist, bspw. auf Basis des Standard-Constraints Pattern mit Hilfe von Constraint Composition. Das Vorgehen dazu ist im entsprechenden Kapitel der Spezifikation recht gut beschrieben.

Was aber, wenn statt der einzelnen Telefonnummer eine Collection von Telefonnummern validiert werden soll? Dazu gibt es leider keine direkte Unterstützung durch den Standard. Für eine solche 'transitive' Validierung ist prinzipiell das Constraint Valid vorgesehen, was aber voraussetzt, dass entsprechende Validierungsregeln für den Elementtyp der Collection definiert sind. Im Beispielszenario ist dieser Typ aber String, hat also keine Gültigkeitsregeln - erst recht nicht für den speziellen Anwendungsfall 'Telefonnummer'.

Derzeit bleibt nur die Möglichkeit, eine individuelle Validierungsmethode in Kombination mit dem Standard-Constraint AssertTrue einzusetzen:

  private List telefonNummern = ...;

  @AssertTrue(message = "{...}")
  protected boolean isTelefonNummernValid()
  {
    if (this.telefonNummern != null)
      for (String telNo : this.telefonNummern.getValues())
        if (!TelNumberValidator.isValid(telNo))
          return false;
    return true;
  }

Hier wäre eine Erweiterung der Spezifikation wünschenswert, z. B. mit Hilfe einer Parametrisierung von Valid:

  @Valid(constraints={@ValidTelNumber}
  private List telefonNummern = ...;

ResourceBundle auf Basis mehrerer Properties

Das übliche Verfahren, lokalisierte Meldungen zu erzeugen, ist die - meist implizite - Nutzung von PropertyResourceBundle. So bezieht bspw. Bean Validation seine Meldungen aus einem Resource Bundle mit Namen ValidationMessages. Dieses materialisiert sich üblicherweise aus Classpath-Ressourcen namens ValidationMessages.properties, ValidationMessages_de.properties etc.

Problematisch ist bei diesem Verfahren, dass z. B. nur eine Datei namens ValidationMessages.properties verwendet wird, auch wenn es davon mehrere im Classpath gibt. Damit ist leider der Weg verbaut, in einer Utility-Bibliothek bspw. Bean Validation Constraints inkl. der zugehörigen Meldungen auf einfache Weise zu definieren:

- Anwendung definiert Validierungsmeldungen als Classpath-Ressource-Bundle "ValidationMessages" (1),
- von der Anwendung genutzte Bibliothek enthält ebenfalls ein Classpath-Ressource-Bundle "ValidationMessages" (2).
- Validierungsfehler nutzen die Meldungen aus (1), aber nicht (2)!

Abhilfe ist hier leicht zu schaffen: Statt der impliziten Nutzung eines PropertyResourceBundle lässt sich eine Lokalisierung von Texten auch durch explizite Bereitstellung eines Resource-Bundles in Form passend benannter Klassen erreichen, d. h. im Beispielszenario können die Klassen ValidationMessages, ValidationMessages_de etc. zur Verfügung gestellt werden (alle im Default-Paket). Die Klassen müssen von java.util.ResourceBundle abgeleitet sein. In ihrem Konstruktor suchen sie nach allen passenden Classpath-Ressourcen und bauen damit einen interne Lookup-Tabelle auf. Sinnigerweise implementiert man dieses Verhalten in einer entsprechenden Basisklasse:

public class ValidationMessages extends MultiPropertiesResourceBundle
{
}

public class ValidationMessages_de extends MultiPropertiesResourceBundle
{
}

...

public class MultiPropertiesResourceBundle extends ResourceBundle
{
private Map messageMap = new HashMap();

public MultiPropertiesResourceBundle()
{
loadProperties(this.getClass().getName().replace('.', '/') + ".properties");
}

public MultiPropertiesResourceBundle(String bundleName)
{
loadProperties(bundleName + ".properties");
}

private void loadProperties(String resourceName)
{
try
{
Enumeration found = Thread.currentThread().getContextClassLoader().getResources(resourceName);
while (found.hasMoreElements())
{
URL url = found.nextElement();
loadMessages(url);
}
}
catch (IOException e)
{
// ignore
}
}

private void loadMessages(URL url)
{
Reader reader = null;
try
{
reader = new InputStreamReader(url.openStream(), "UTF-8");

Properties prop = new Properties();
prop.load(reader);

for (Entry entry : prop.entrySet())
{
String key = (String) entry.getKey();
if (!this.messageMap.containsKey(key))
{
String value = (String) entry.getValue();
this.messageMap.put(key, value);
}
}
}
catch (IOException e)
{
// ignore
}
finally
{
try
{
reader.close();
}
catch (Exception e)
{
// ignore
}
}
}

@Override
public Enumeration getKeys()
{
ResourceBundle parent = this.parent;
return new ResourceBundleEnumeration(this.messageMap.keySet(), (parent != null) ? parent.getKeys() : null);
}

@Override
protected Object handleGetObject(String key)
{
return this.messageMap.get(key);
}
}

Criteria Queries in JPA 2.0

JPA 2.0 bietet als eine der groß angekündigten Neuerungen sog. Criteria Queries an. Damit ist es möglich, Queries in objektorientierter Weise zusammenzusetzen und dabei die Vollständigkeit und die Typkorrektheit weitgehend zu garantieren.

Eine herkömmliche Query sähe bsw. so aus:

TypedQuery q = em.createQuery("select e from SomeEntity e where e.name='Hugo'", SomeEntity.class);

Das ist zwar relativ einfach und überschaubar, birgt aber ein paar Stolperfallen:
- Ist die Query formal vollständig?
- Passen die selektierten Werte zum Typ der Query?
- Heißt das Attribut wirklich name?
- Hat das verglichene Attribut wirklich den Typ String?

Nichts davon kann zur Compilezeit geprüft werden, da dazu der JPQL-Text geparst werden muss. Das hier eingesetzte, neue, getypte Interface TypedQuery hilft hier auch nicht weiter; seine Vorteile liegen erst in der hier nicht gezeigten weiteren Verarbeitung der Query.

Als Criteria Query sähe das Beispiel so aus:

CriteriaBuilder builder = em.getCriteriaBuilder();

// Criteria Query für Ergebnistyp erzeugen
CriteriaQuery cQuery = builder.createQuery(SomeEntity.class);

// Projektionsvariablen erzeugen (FROM-Klausel)
Root c = cQuery.from(SomeEntity.class);

// Selektion angeben (SELECT-Klausel)
cQuery.select(c);

// Bedingung erstellen und der Query hinzufügen
Path cName = c.get(SomeEntity_.name);
Predicate hatNamen = builder.equal(cName, builder.parameter(String.class, "name"));
cQuery.where(hatNamen);

// Normale Query erstellen
TypedQuery q = em.createQuery(cQuery);

Was hat's nun gebracht?

Zunächst kann man durch die geschickte Verflechtung der zu nutzenden Typen kaum etwas vergessen, die Query wird also formal vollständig sein. Man könnte im Beispiel höchstens die SELECT-Klausel vergessen, was zur Compilezeit deshalb nicht geprüft werden kann, da eine Query ja nicht in jedem Fall etwas selektiert.

Im Falle der SELECT-Klausel sieht man aber bereits einen Vorteil: Versucht man hier etwas zu selektieren, was nicht zum Typ der Query passt, ist ein Compile-Fehler die Folge.

Beim Aufbau der WHERE-Bedingung, genauer des darin genutzten Prädikats, werden zwei weitere Vorteile deutlich: Zum einen wird durch die Verwendung des statischen Metamodells - hier in Form der Klasse SomeEntity_ - sicher gestellt, dass das verwendete Attribut der Entity existiert. Zum anderen wird auch hier Typsicherheit erreicht, ebenfalls durch das Metamodell, das für das Attribut name den Typ String "kennt".

Die Criteria Query erzeugt also zwar einen höheren Schreibaufwand als die herkömmliche Query, bietet dem Entwickler aber eine deutlich höhere Sicherheit, korrekten Code geschrieben zu haben.

Das gezeigte Beispiel setzt allerdings voraus, dass zu jeder Entity-Klasse E eine statische Metamodell-Klasse E_ existiert, die die Metadaten der persistenten Attribute von E enthält. Das statische Metamodel kann man theoretisch manuell erstellen, sinnvoll ist aber m.E. nur die automatische Generierung. Die gelingt durch Annotations-Prozessoren, die seit Java 6 wie Plugins in den Compilerlauf eingeschoben werden können. So kann man bspw. den Hibernate Static Metamodel Generator verwenden. Dies funktioniert auch, wenn nicht Hibernate als JPA-Provider genutzt werden soll, da das Metamodell Provider-übergreifend spezifiziert ist.

Der Einsatz des Hibernate Static Metamodel Generator ist auf der folgende Webseite recht gut erläutert:
http://relation.to/Bloggers/HibernateStaticMetamodelGeneratorAnnotationP...

Neuen Eintrag erstellen

Der Inhalt dieses Feldes ist privat und wird nicht veröffentlicht.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <hr>
  • Lines and paragraphs break automatically.

More information about formatting options