Java Persistence erlaubt den Einsatz von Embeddables als persistente Eigenschaften von Entities, z. B. so:
@Embeddable public class Adresse { private String strasse; private String hausNummer; private String plz; private String ort; ... } @Entity public class Person { ... private Adresse adresse; ... }
Leider definiert die Spezifikation JPA 2.0 nicht eindeutig, wie beim Laden von Objekten verfahren werden soll, bei denen sämtliche Attribute des Embeddables null sind: Soll im Beispiel trotz leerer Adresse ein Adress-Objekt instanziert werden oder bleibt das Attribut
Person.adresse selbst null?
Eclipselink und Hibernate tun letzteres, d. h. vollständig leere eingebettete Objekte werden nicht instanziert.
Das Verfahren mag bei Betrachtung des Persistenzanteils einer Anwendung so in Ordnung sein, erzeugt aber für GUI-Frameworks Probleme, die ein Data Binding benutzen, also bspw. Eingabefelder direkt mit den entsprechenden Bean-Attribute verknüpfen. So könnte bspw. in einer JSF-View der folgende Ausdruck für die Eingabe der Postleitzahl – also eines Teils der eingebetteten Adresse – einer Person verwendet werden:
<h:inputText value="#{personModel.person.adresse.plz}" />
Für den oben skizzierten Fall einer bislang noch komplett leeren Adresse erzeugt der JSF-EL-Ausdruck eine NullPoinerException, da person.adresse noch null ist.
Nun könnte man auf die Idee kommen, das betroffene Attribut in einer Lifecycle-Methode nach dem Laden bei Bedarf zu instanzieren:
@Embeddable public class Adresse { ... @PostLoad private void postLoad() { if (this.adresse == null) this.adresse = new Adresse(); } }
Dadurch würde aber beim Speichern des Objektes stets ein unnötiger Update ausgelöst, da es aus Sicht des Persistenzproviders ‚dirty‘ ist. Würde man zur Behebung dieser Situation auch die umgekehrte Lifecycle-Methode vor dem Speichern so programmieren, dass das Attribut – soweit immer noch leer – wieder auf null gesetzt wird, käme man danach wieder in das Problem der NullPointerExceptions.
Eine funktionierende Lösung ist daher ein Late Initializing des Attributs in seiner Getter-Methode – nun allerdings threadsafe mittels Double Check Lock Idiom nach Joshua Bloch:
@Entity(name = "Person") public class Person { ... private volatile Adresse adresse; ... public Adresse getAdresse() { Adresse tmp = this.adresse; if (tmp == null) { synchronized (this) { tmp = this.adresse; if (tmp == null) { tmp = new Adresse(); this.adresse = tmp; } } } return tmp; } ... @PrePersist @PreUpdate private void preSave() { if (this.adresse != null && this.adresse.isNull()) // Prüfen, ob Adresse komplett null this.adresse = null; }
Wichtig dafür: Das Attribut adresse muss volatile sein, sonst funktioniert das DCL-Idiom nicht zuverlässig!
Man sieht: Ziemlich vel Aufwand für so eine einfache Angelegenheit. Es wäre also schön, wenn JPA-Provider sind bei Embeddables anders verhalten würden. Man könnte ja bspw. in die entsprechende Annotation noch einen Parameter aufnehmen, der den Lademodus bestimmt:
@Embedded(loadIfNull=true)
EclipseLink bietet über sein Feature @Customizer eine ähnliche wirkende Konfigurationsmöglichkeit an (http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Embeddable).
Dirk
2 Kommentare. Hinterlasse eine Antwort
Ich habe mich heute eher zufällig mit der Initialisierung von @Embeddable-s beschäftigt und Dein Blog war der einzige informative Beitrag, den ich gefunden habe.
Meine Frage: Was spricht dagegen die @Embeddable Objekte einfach gleich zu instanziieren?
Das ist tatsächlich eine Möglichkeit – allerdings in der @PostLoad-Methode, nicht etwa im (NoArg)Konstruktor. Mit dem Lazy Initializing lässt sich verhindern, dass der Provider das Objekt unmittelbar als Dirty markiert und dann am Ende der Transaktion ein UPDATE ausführt. Das würde bei meinem Ansatz erst dann passieren, wenn tatschlich auf das Embeddable zugegriffen wird. Der Trick, in @PreUpdate das entsprechende Attribut wieder auf null zu setzen, wenn es nicht anderweitig besetzt wurde, und damit das unnötige UPDATE zu verhindern, funktioniert leider providerabhängig nur in manchen Fällen.