JPA: Verweise auf Ressourcen persistieren

Wie persistiert man am besten Verweise auf Ressourcen, die irgendwo abgelegt sind, in einer Datenbank?
Beispielanwendungen:

  • Link auf eine Webseite
  • Verweis auf eine lokal abgelegte Datei (die Datei selbst soll nicht in der DB gespeichert werden!)

Ich hatte dabei an sowas gedacht, wie eine Ressourcen-Abstraktion, die dann per Property-Access in ein String-Feld gespeichert wird. Dazu müsste man aber überall, wo die Ressource verwendet wird, die Übersetzung von String zu Ressource und umgekehrt machen.
Besser wäre aber vielleicht einen neuen Hibernate-Typ einzuführen (siehe hier: http://docs.jboss.org/hibernate/core/4.3/manual/en-US/html/ch06.html#types-custom). Im Gegensatz zum Ansatz mit dem Property-Access wäre das dann zwar vendorspezifisch, aber meines Erachtens nach deutlich eleganter, da es nur an einer Stelle implementiert werden muss und dann überall transparent verwendet werden kann.

*** Edit ***

Hmm… Die Klasse URL scheint mir als Abstraktion geeignet zu sein und wird (zumindest von Hibernate) auch passend persistiert.

Über Best Practices würde ich mich dennoch freuen.

Die Klasse URL ist etwas tricky. Z.B. wären die URLs http://localhost/irgendwas und http://127.0.0.1/irgendwas äquivalent. Sie zeigen nämlich auf dieselbe Resource. Das wird in der equals-Methode von URL auch berücksichtigt. Bei richtigen Hostnamen führt der Equals-Vergleich darum zu einem DNS-Lookup. Einzeln nicht schlimm, aber wenn man bspw. in ein Set von URLs einfügt, macht sich das auf jeden Fall bei der Perfomance des Programms bemerkbar… Und, wenn DNS mal nicht funktioniert?.. Außerdem wären o.g. URLs in der Java-Welt gleich, in der DB-Welt aber ungleich. Das könnte einen überraschen, wenn etwa ein SELECT COUNT… etwas anderes ergibt als ein Set.size() obwohl man doch den UNIQUE CONSTRAINT gesetzt hat…

Deswegen ist die bessere Wahl die Klasse java.net.URI. Das ist im Prinzip die Abstraktion eines Strings, bei der die allgemeinen Regeln für URIs erzwungen und praktische Hilfsmethoden zur Verfügung gestellt werden.

Edit: Sehe gerade, dass Du Dir dafür aber einen eigenen UserType schreiben müsstest. Im Gegensatz zu URL hat Hibernate dafür keinen eigenen. Ist aber nicht schwer.

Der Hinweis mit dem Lookup ist wirklich wichtig. In dem Umfeld, in welchem ich die Ressourcen einzusetzen gedenke, werden nicht normalisierte URIs wohl eher die Ausnahme denn die Regel sein. Die UserTypes sollten wohl kein Problem sein, ich habe mir mal ein paar Implementierungen angesehen.

Du scheinst es aber auch für sinnvoll zu erachten, sich lieber an Hibernate zu binden, als irgend ein gewerkel mit Property-Access?

Im Prinzip ist der UserType ja nix anderes als ein URI.toString()/Uri.create verpackt in etwas Hibernate, damit man es in der Anwendung nicht sieht. Finde es nicht so schlimm, sich mit sowas an Hibernate zu binden. Das wäre bei einem Wechsel des Persistence Providers sicher schnell ausgebaut. Andere Hibernate-Spezifika sehe ich da wesentlich kritischer.

Nur am Rande: Bevor ich wusste, wie UserTypes funktionieren, habe ich übrigens mit public Gettern/Settern für den richtigen Java-Datentyp und protected Gettern/Settern plus Annotation für die Persisitierung des DB-Kompatiblen gearbeitet. Das erschien mir damals das beste. Sieht zwar im Code nicht so schön aus, aber da die Persistenz-Getter/Setter protected waren, hat man es von außen wenigstens nicht gesehen. Ach ja, das wäre übrigens mein zweiter Best-Practice-Tipp: Verwende Getter-Mappings und nicht fieldbased.

Weshalb? Ich mappe derzeit fieldbased, weil ich die Getter häufig nicht benötige. Und selbst wenn sie protected sind, “verunstalten” sie den Code mehr, als implizites fieldbased mapping.

Getter-Mapping nutze ich, wenn ich einen Setter habe und darin Logik enthalten ist.

Hibernate arbeitet (falls sich das nicht in den neuen Versionen geändert haben sollte) für die Erzeugung der Proxy-Objekte mit Vererbung. Das ist der Grund, warum Entities nicht final sein können. Da die Felder i.d.R. private sind, muss es per Reflection die Felder zunächst ermitteln und dann die Sichtbarkeit erhöhen. (Zur Überprüfung, lass Deine Anwendung mal mit aktivem Security Manager laufen und schaue, was da für SecurityExceptions geworfen werden) Außerdem lassen sich Felder nicht innerhalb der Vererbungshierarchie überschreiben, so wie das bei Methoden geht. Wenn man über Methoden (die Getter) mappt, kann man über die überschriebenen Methoden erweiterte Mappings setzen. Beim reinen Datenmapping mag es recht selten vorkommen, dass man das möchte. Da Hibernate aber auch die BeanValidation-Annotationen bei der Erstellung der DDL berücksichtigt, erhöht sich die Wahrscheinlichkeit, dass man innerhalb der Vererbungshierarchie andere Annotationen setzen möchte. Bei fieldbased Mapping geht das nicht. Und dass Du Getter nicht brauchst, ist doch eher die Ausnahme oder? Was haben den Felder für einen Sinn, wenn man sie von außen nicht sieht. Außer bei der VersionId und evtl dem Primärschlüssel fällt mir gerade kein Beispiel ein, wo man ein nicht sichtbares Feld hätte… Gibt es sicher, aber wie gesagt wohl eher die Ausnahme oder?

Das verstehe ich nicht so ganz. Ich nutze es kräftig aus, dass Hibernate die Validation-Annotationen berücksichtigt. Und das funktioniert auch alles so, wie es soll. Die Validation-Annotationen brauche ich nicht mehr, wenn das Objekt aus der Datenbank kommt, sondern nur, wenn es durch den Anwender eingegeben wird. Und da ist noch kein Proxy im Spiel.

Nein, das ist schon recht häufig der Fall. Der von außen Sichtbare Zustand unterscheidet sich vom internen. Collections gebe ich zum Beispiel prinzipiell nicht nach außen. Außerdem kommen zu den Gettern ja auch noch die Setter, die definitiv nicht benötigt werden.
Meine Objekte haben einen protected Konstruktor ohne Parameter und einen öffentlichen, mit allen notwendigen Attributen. Gerade bei Value-Objects sind das alle, die den Zustand ausmachen, da sie immutable sind. Ok, da gibt es dann aber auch fast immer einen Getter, der das unterliegende Feld zurückgibt - und sei es nur als Kopie - aber eben keine Setter. Und bei den echten Entities ist der Zustand bestmöglich gekapselt.

Du brauchst mir das aber nicht alles vorzukauen. Vielleicht kennst du ja eine gute Quelle, in der das erklärt ist.

*** Edit ***

Was für erweiterte Mappings meinst du?

Und danke für die ausführlichen Antworten!

Mal beispielhaft: Ich habe eine abstrakte Klasse NamedEntity mit getName(). Bei einigen Entites darf der Name null sein, bei anderen nicht. Bei einigen Entities muss der Name unique sein, bei anderen nicht. Also in der abstrakten Klasse das Defaultverhalten mit keinen Einschränkungen und in den Subklassen, wo es gebraucht wird der überschriebene Getter mit @NotNull und @Column mit entsprechenden Parametern.

Und zu Deiner Frage nach Quellen: Da ergibt sich ein recht widersprüchliches Bild. Insofern kann man das von mir favorisierte Verfahren wohl nicht als Best-Practice sondern eher nur als nillehammer-Practice bezeichnen :wink:

Das habe ich jetzt verstanden. Ich wusste gar nicht, dass man die validation-constraints auch an einen Getter schreiben kann - wieder was gelernt.

Ok. Es wird wohl darauf hinauslaufen, dass ich Propertymapping nur dann verwende, wenn ich
[ol][li]sowieso Getter und Setter habe, oder
[/li][li]ein besonderes Mapping erforderlich ist, welches nicht durch einen UserType abgebildet wurde.[/ol]
[/li]Den Rest bringt dann wohl die praktische Erfahrung mit sich.