JPApi #

Soll eine Bibliothek für eine "Java Persistenz API" werden, die flach und einfach ist,

Es kann losgehen, ich brauche nun eine Abstraktion, für mich privat (Homepage) und für ein paar Freunde. (JensKapitza)

Nach einer Besprechung beim LUG-Treffen wollten wir dieses Thema mal wieder aufwärmen.

JPApi.Lizenz GPL. (U.u. BSD, LGPL)
Projektname JProxyApi, SwingingBeans
SVN http://svn.bayen.mine.nu/svn/tbayen/trunk/SwingingBeans/
Entwickler JensKapitza, ThomasBayen,wer noch?
OpenJPA hmm muss man sich mal näher angucken

ProblemeLösung
UTF-8, ich bin doch ISO nutzer ;) Umlaute sind schön!Ist im Projekt jetzt fest eingestellt, bitte updaten

Projektname #

Also den Projektnamen JPApi nehme ich nicht. Der ist ja hässlich. Da werden wir im Limericks nochmal das eine oder andere Guinness drüberlaufen lassen müssen. :-) Ein öffentliches SVN habe ich bei mir. Wenn das Projekt in Fahrt kommt, können wir es auch nach Sourceforge transferieren (in lug-kr oder einem eigenen Projekt).

mach einfach einen Vorschlag -- JensKapitza
Im Moment fallen mir folgende Ideen ein:
  • Wir benutzen mein bestehendes Projekt "SwingingBeans". Der Name ist schön und witzig, mein SVN etc. steht schon. Meine bisherigen Persistenz-Klassen befinden sich dort bereits. Der "Haken" ist, daß ich meine Swing-Visualisierung (die auf der Persistenzschicht aufbaut) mit in diesem Projekt habe (und weiter haben werde). Natürlich sollten wir unterschiedliche Pakete benutzen und wir können im Build-Skript natürlich auch gerne getrennte JAR-Dateien erzeugen. Einer späteren Abspaltung in ein eigenes Projekt steht nichts im Wege (z.B. wenn uns ein anderer, besserer Name eingefallen ist). Diese Methode ist schnell und unkompliziert und ist im Moment mein Favorit. Siehe http://swingingbeans.javaproject.de/ -- ThomasBayen
  • Phaseolus: persistent holding and saving of entity objects - light, usable and secure. Siehe http://de.wikipedia.org/wiki/Phaseolus_vulgaris -- ThomasBayen
  • Perrijer - Persistenz vom Jenz :-)
  • S.O.B. - Save Our Beans

Ziele, die man erreichen sollte #

  • kompatibel mit JDK >= 1.5
  • wenige bis hin zu gar keinen Abhängigkeiten
  • Möglichst wenig Konfiguration
  • Annotationen an der vorhandenen API von Sun orientieren (EJB)
  • unsere API an der API von Sun orientieren (?)
  • kurze Ladezeit (im Vergleich z.B. zu Hibernate)
  • ClassLoader, Interfaces und Klassen auch drüber hinaus aus lesbar machen (Konfigurations Klasse, * addClass() *)
  • so weit wie möglich Nutzung vorhandener APIs (z.B. BeanInfo, PropertyDescriptor, Collections wie List oder Set, etc.)
  • Laszy Listen, Sets, ...
  • SQL als Abfrage sprache
  • Object Cache, nur änderungen in Datenbank speichern
  • Treiber unabhängig, eigene Driver.class als Proxy nutzen
  • reconnect, oder STATEMENTS doppelt absetzen bei einem Ping timeout
  • Funktionsfähigkeit in JavaSE und in JavaEE
  • automatische Erstellung von neuen Datenbanken
  • automatisierte Erzeugung von Swing-Widgets zur Bearbeitung von Daten

Features #

  • Mapping
  • Caching (PREPARED Statement)
  • Connection POOL
  • Konfiguration durch Java Quellcode, Konventionen, Annotationen, Konfigurationsdateien, vorhandene Datenbank (in dieser Reihenfolge)
  • Im Normalfall stehen alle Eigenschaften einer Bean in einer einzigen Datei (im Java-Quelltext)
  • automatische Erstellung von GUIs (Web und Swing)
  • Arbeit mit mehreren Datenbanken gleichzeitig (mit übergreifenden Fremdschlüsseln)
  • Caching von Metainformationen

Observer-Pattern (Wikipedia) #

method.insertBefore ("delegate('" + methodeName + "',this);");
  • Es sollte möglich sein, den Observer auch selbst zu implementieren (ganz ohne BCEL) - das macht dessen Funktionsweise transparenter.
ThomasBayen ist inzwischen auf die Java Compiler API umgestiegen. Die hat den einen Vorteil, daß man außer einem JDK nichts weiter benötigt (aber auf einem JRE natürlich auch eine Bibliothek einbinden kann) und den anderen, daß Java als Sprache für meinen Bedarf viel logischer ist als künstlich irgendeine Skriptsprache zu lernen. Die Lösung ist viel langsamer als die vorherige in BCEL, aber mit einem classfile-Cache geht das. Dafür ist sie viel wartungsfreundlicher! Wer Klassen on-the-fly im Speicher (also komplett ohne Dateizugriffe) erzeugen will, sollte mich fragen. Das ist recht kompliziert, nirgendwo richtig erklärt, geht aber. -- ThomasBayen

Was nimmt man für die Modifikation der Beans #

  • Die o.g. Bytecodegeneratoren können unser Problem wohl alle lösen. Allerdings ist die Codeerzeugung alles andere als intuitiv und einfach. Andererseits kann man durch Delegation dazu kommen, daß man die eigentliche Funktionalität wieder in richtigem Java schreiben kann. Man kann die persistente Klasse als Ableitung der Original-Beanklasse implementieren. Wenn diese Lösung einmal steht (ich habe es bereits mit BCEL gemacht), ist sie eigentlich ganz gut.
  • Proxy-Objekte bauen auf einem Interface statt auf einer Bean-Class auf. Man müsste also zu jeder Beanklasse ein identisches Interface haben. Vielleicht erstellt man dieses per BCEL? Es entsteht dann das Problem, daß das Proxy-Objekt nicht mehr die Klasse der ursprünglichen Bean hat. Das kann zu Verwirrung führen. Hibernate scheint so zu arbeiten, da mir dort genau dieses Problem aufgefallen ist.
  • AspectJ ist garantiert die eleganteste und intuitivste Lösung. Scheinbar kann man es neuerdings sogar dazu bringen, in einer Standard-Umgebung zu laufen (ohne Precompiler). Es erlaubt aber leider nicht, zusätzliche Methoden in einer Klasse zu erzeugen. Es modifiziert immer die Originalklasse, d.h. auch nicht-persistente Instanzen der Klasse werden verändert.
Es ist auf jeden Fall besser, den Observer in einer Programmiersprache zu schreiben und dann zu übersetzen. Ich habe hier eine laufende Lösung, die direkt BCEL benutzt und das Ding ist von der Wartbarkeit ein Monster. Eine Sourcecode-Lösung gibt uns auch die Möglichkeit, den erzeugten Source zu speichern und dann vom Programmierer anpassen oder ableiten zu lassen, um besondere Hacks zu ermöglichen. D.h. der Observer-Generator arbeitet nur dann, wenn kein Observer bereits vorhanden ist.

Da ja leider in Java kein Java-Compiler enthalten ist, aber in Java6 Rhino (Javascript Engine) enthalten ist, ist das vielleicht die richtige Sprache. Java als Sprache hätte den Vorteil, daß die o.g. angepassten Observer ganz normal in den Sourcecodebaum kommen könnten.

Ein Kriterium könnte noch sein, ob es unter JavaWebStart ohne weitere Sicherheitsfreigabe lauffähig ist (wäre bei Java einfach, bei Rhino müsste man das mal testen).

Ach ja - meine BCEL-Lösung ist eine Ableitung der Beanklasse, um die es geht, die ein besonderes Interface implementiert. Hibernate hingegen benutzt Proxy-Objekte. Mir ist der Sinn der Proxy-Lösung noch nicht aufgefallen. Im Gegenteil haben die persistenten Objekte dann ja eine komplett andere Klasse. Kennt jemand ein Argument für Proxys?

die Klasse selber muss dem Server nicht bekannt sein. Wenn man also mehrere Clients hat (verschiedene Klassen) können diese alle die selben Prox-Objecte geschickt bekommen und damit arbeiten. Wenn aber Alles abgeleitet wird, dann muss das jeder Client machen und man verwaltet dann evtl. mehrere Objecte. (Auser man leitet immer von einer speziellen Klasse ab.) -- JensKapitza
Irgendwie verstehe ich nicht genau, was Du meinst. Was ist ein Server und ein Client? Meinst Du RPC? Da hast Du wohl recht. Ableitung bedeutet, daß es zu jeder Klasse, z.B. "Adresse" eine abgeleitete Klasse "AdressePersistent" gibt, die die Persistenz enthält. Ich glaube übrigens nicht, daß man Proxy-Objekte so einfach per RPC verschicken kann, ohne die echten Objekte mitverpacken zu müssen. Das ist dann evtl. auch nicht weniger kompliziert. -- ThomasBayen
ich verstehe die Sache mit dem Class Loader zwar, aber das geht/ging bei mir bislang immer nie. -- AspectJ nutzt auch BCEL -- JensKapitza
Ich habe es (mit Widerständen) ans laufen gebracht und damit bewiesen, daß es grundsätzlich geht :-) Aber je nachdem, auf welcher Basis wir das jetzt implementieren wollen, müsste man das wohl entsprechend nochmal machen.

Problem mit der Umwandlung der Objektklasse

Ich habe ein Problem gefunden, das ich spontan nicht lösen kann. Laut JPA kann man ein Objekt mit

  Player p = new Player();

erzeugen und dann mit

  EntityManager em = ...
  em.persist(p);

persistent machen. Das verstehe ich nicht ganz. Vorher ist p ja wohl einwandfrei ein ganz normales Player-Objekt ohne irgendwelche Besonderheiten. Nach dem persist-Aufruf müsste es doch dann ein persistentes Spezialobjekt (also eine Ableitung, ein Proxy oder sowas) sein. Wie kann denn die Methode em.persist die Klasse von p verändern?!?

Inzwischen habe ich mir mal die Mühe gemacht und die Glassfish-Implementation installiert und getestet. Die benutzt keine Proxy-Objekte oder ähnliches. Ich weiss allerdings genau, daß ich mit Hibernate mal darauf gestossen bin. Mag sein, daß ich jetzt den Wald vor lauter Bäumen nicht mehr sehe, aber: wofür braucht eine JPA-Implementation das Observer-Pattern? ICH brauche die, weil eine Swing-GUI sowas wie autocommit braucht (und ich Listener benutze), aber davon ist in JPA keine Rede. Hmmmm..... -- ThomasBayen

Wahlurne #

Vielleicht ist es langsam an der Zeit, einige Threads zusammenzufassen und ein paar grundlegende Entscheidungen zu treffen:

Wie werden Observer in die Klassenstruktur eingebaut?

  • Als abgeleitete Klassen der Basis-Beanklasse
  • Als Proxy-Objekt (wie z.B. in Hibernate)
  • Als Modifikation der Basis-Beanklasse (wie z.B. in AspectJ)
ThomasBayen ist für die Ableitung.

Was benutzen wir zur Erzeugung von Observern?

  • Erzeugung von Java-Quelltext (benötigt eingebetteten Java-Compiler)
  • Erzeugung von Javascript (benötigt Java6, das Rhino enthält)
  • Erzeugung von Groovy-Quelltext (benötigt Java6 und groovy)
  • Erzeugung der Klassen direkt mit BCEL (benötigt BCEL-Bibliothek)
ThomasBayen schwankt zwischen Java (weil das nicht wieder eine neue Sprache ist) und Rhino (weil man da keine Bibliothek benötigt und Javascript auch nicht sooo schlimm ist) mit leichter Präferenz für Rhino.

Design-Entscheidungen #

Hier möchte ich kontroverse Dinge sammeln bzw. Entscheidungen dokumentieren, die nicht sofort offensichtlich sind.

Wie direkt ist die Verbindung zur Datenbank? #

Eine Möglichkeit ist es, unabhängige Beans (mit Fremdschlüsseln ggf. einen Beanbaum) zu laden. Diese werden dann frei bearbeitet und werden mit einer update-Methode wieder zurückgeschrieben. Das andere Extrem ist es, wenn jeder getter und setter der Bean direkt auf einen SQL-Befehl gemappt wird. Zwischenlösungen sind denkbar. Für verschiedene Probleme sind verschiedene Bindungsstärken sinnvoll. Was sollte nun wirklich implementiert werden?

Nach Lesen der JPA-Spezifikation habe ich bemerkt, daß das ein Problem der Datenbank-Transaktion ist. In einer JavaEE-Umgebung ist das ganze wohl egal, in JavaSE sollte man entweder eine Möglichkeit haben, die Transaktion auf "autocommit" zu stellen, oder in der GUI darauf achten, daß nach jedem Klick brav committet wird. Was besser ist, bin ich noch unentschlossen.

Was ist eine Bean? #

Eine Klasse mit gettern und settern, um auf bestimmte Eigenschaften zuzugreifen. Beispiel:

public class Bean {

    private Feld a;

    public Feld getA()  { return a; }
    public setA(Feld b) { a = b;   }
}

Klassenstruktur #

Ich denke, wir sollten damit anfangen, die Teile der JPA-Spezifikation (JSR 220 lesen!) zu implementieren, die wir für unsere konkreten Anwendungen benötigen.

Sind wir schon soweit, daß wir eine grobe Klassenstruktur angeben können und dann vielleicht auch schon Aufgaben verteilen? Im Moment fällt es mir noch schwer, das Problem zu fassen und in zwei oder mehr Teile zu zerlegen.

Implementation von Collections #

Jens schlug vor, als Basis einer Collection das ResultSet zu nehmen. Leider steht - wie ich vermutete - in der ResultSet API, daß die Methoden zur Navigation in ResultSets eine SQLFeatureNotSupportedException werfen dürfen, wenn sie vom JDBC-Treiber nicht unterstützt werden.

also ich habe mal mit HSQL das ausprobiert, HSQL kann das ja auch, wiso sollen wir dann auf sowas verzichten? ich kenne spantan keine DB die man für sowas benutzt, die das nicht kann.
Das bedeutet, man kann sich hierauf nicht verlassen. Andererseits dürfte das da, wo es geht, eine überlegene Performance haben. Mein Vorschlag ist, wir nehmen nicht das ResultSet als Basis, sondern das Query. Dann cachen wir das sich ergebende ResultSet, solange es sinnvoll und mit diesem Treiber möglich ist.

Apropos cachen sinnvoll: Es bleibt der Unterschied, daß ein ResultSet IMO seinen Inhalt nicht ändert, ein Query bei jedem Zugriff anders aussehen kann. Beides kann sinnvoll sein. Was passiert denn mit einem ResultSet, wenn die Transaktion abgeschlossen ist? Wenn man das RS-Objekt stundenlang behält, hat man dann immer noch Zugriff auf alle alten Daten?!? Ist das irgendwo spezifiziert? -- ThomasBayen

Diskussion #

JSP EJB und alles andere NEWS oder keine ahnung wohin damit#

Mann, das waren aber viele Wörter ohne Absatz. :-( Ich versuche mal, zu sortieren und zu antworten:
hmm ich habe mal ein wenig in dem JSR gelesen was eigentlich so verlangt wird. Programmieren wir nicht irgendwie an irgendwas herum? getSingleResult ist eine Zeile als Object es geht alles um EJB also muss man nicht nur seine *eigene Query language* schreiben sondern auch irgendwie alles managen, es ist aber nicht wirklich gans so das, was ich haben will
Vielleicht muss da wirklich etwas klargestellt werden: Ich habe nicht wirklich vor, die ganze Spezifikation zu erfüllen (das haben andere schon gemacht). Ich möchte ein ganz eigenes Framework schreiben. Dabei brauche ich für mich z.B. keine eigene Query-Language. Die Dinge, die sich mit JPA überschneiden, möchte ich aber kompatibel halten, um nicht nachher als völliger Eigenbrötler dazustehen.
es fehlt auch in dieser JSP etwas was ich haben will, wir Observer Pattern ja, ist schon was feines aber es kann nur gehen wenn Objecte niemals Detatched werden.
Genau so ist das. Das sollte man so einschalten können. Wobei ein Modus, der Swing-Programme unterstützt, auch sowas wie autocommit benötigt. Damit sollte es kein Problem sein, Objekte länger detached zu lassen. Man braucht halt eine Exception, falls einem Jemand irgendwas unter dem Hintern weggezogen hat, aber das liegt in der Natur der Sache.
LOCK von Objecten ist naja von der Datenbank zu machen *das scrollen in einem Resultset auch* nunja worüber man sich streiten kann ist das eine verbindung nicht ewig hält, und ein nutzer wissen solte, ob er Lesend oder schreibend diese objekte nutzen will, oder dass man ihm dennoch eine schreibende methode gibt die alle Objekte in die datenbank schreibt/updatet
Ich bin dagegen, ein ResultSet länger als vielleicht eine Sekunde lang aufrecht zu erhalten. Das muss erstens irgendwo gepuffert werden und kostet damit Speicher und bringt dem Benutzer ja auch nichts, wenn er eine Stunde später die GUI weiterbenutzt. Im Grunde sollte die Swing GUI jedes Mal, wenn die Event-Queue abgearbeitet ist und sie sich schlafen legt, keine Daten mehr gespeichert haben (abgesehen vielleicht von Ausnahmen in einem internen Cache).
Die Observer-Objekte sollten kein MUSS sondern ein KANN sein. Wer nur lesend zugreift, braucht sowas nicht und bekommt Kopien.
eine Konfiguration von Objekten selber finde ich auch recht überflüssig ist es nicht einfacher, wenn wir davon ausgehen, dass jedes Object was wir in unsere schnittstelle geschoben bekommen für/von der datenbank ist.
Ich möchte auch nichts konfigurieren. Das minimale Zugeständnis an JPA ist @Entity und wenn Du willst, können wir das auch optional machen.
ein weiteres problem sehe ich in welches object in welche datenbank , .... ... :::TODO:::
Da sollten wir eine brauchbare Annotation für definieren. So ähnlich, wie der Tabellenname definiert wird. Intern wird's wohl etwas aufwendiger, weil viele Datenbanken dann keine Fremdschlüssel mehr unterstützen.
es ist doch einfacher wenn ich in meiner anwendung mit meinen beans nichts konfigurieren muss,
Da sind wir uns völlig einig. Eigentlich soll sich alles von alleine konfigurieren. Wie kommst Du auf die Idee, es könnte anders sein?
ich will ableitungen in der datenbank speichern ja, aber doch nicht mit hunderten von annotationen den kram erreichen, ist es so schwer eine wenig KI ins spiel zu bringen, wenn ein Object abgeleitet ist und es eine tabelle gibt, oder die spalten der felder der super klasse vorhanden sind in meiner tabelle, diese zu setzen?
Also um Ableitungen habe ich mir noch keine übertrieben ausführlichen Gedanken gemacht. Sollten wir das für nötig halten, würde ich Vorschlagen, das wir uns für eines der Persistenz-Modelle entscheiden (z.B. die ganze Klassenhierarchie in eine Tabelle oder jede Klasse hat eigene Tabellen etc.) und Punkt. Da sehe ich spontan nicht, wo man da eine einzige Annotation brauchen würde?!?
das was ich für sinvoller halte ist eine annotation zu definieren, die alles was nicht zur datenbank gehört zu markieren, nicht was aus der datenbank ist,
Was Du meinst, ist @Transient
ich kann alle felder lessen, alle methoden, alle super klassen finden, dessen methoden und felder wieso muss ich dann sagen dass wenn ich eine Liste als feldtyp angegeben habe dass es ein 1-n n-m oder was auch immer ist, wenn ich eine liste angebe und sage @NotAtDatabase angebe weiß ich dach dass es nicht aus der datenbank kommt, und ansonsten weiß ich, dass es eine Liste geben muss vom Type T da wir Generics verwenden!! ich geben mal wieder später meinen SENF dazu.
Was Du IMHO meinst, ist, daß man @OneToMany und @ManyToOne weglassen könnte. Das ist eine gute Idee. Man müsste eine Regel für die beteiligten Spaltennamen festlegen, die sonst als Parameter angegeben werden.

Tags:  Java, Datenbank

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-34) was last changed on 19-Feb-2008 21:56 by PeterHormanns