mein Projekt wird immer größer. Jetzt habe ich mir persönlich eine Frage zur Generalisierung gestellt und würde gerne euere Meinung dazu hören.
[ul]
[li]Ich habe zig Entitäten. Alle Entitäten haben gemein das sie über einen Primary Key des Types Integer verfügen.
[/li][li]Ich habe sehr viele Entitäten die über Attribute zum speichern des Erzeugers mit Datum haben
[/li][li]Etwas weniger als die vorherigen Entitäten besitzen auch noch Attribute die Auskunft über das letzte Änderungsdatum geben
[/li][li]Dann gibt es einige Entitäten die zusätzlich noch ein Löschdatum - Attribute verfügen etc. etc. etc.
[/li][/ul]
Jetzt habe ich mir gedacht wieso leite ich diese nicht voneinander ab und füge den abgeleiteten Klassen die jeweiligen Getter-/Setter - Methoden hinzu. Was haltet ihr davon? Wo fängt bei euch die Generalisierung an und wo hört sie auf?
Generell ist es möglich und sicherlich auch Praktisch dort eine Vererbung mit einzubauen, um dir Codedopplungen zu sparen. Bei uns im Projekt haben wir das auch gemacht.
Die Frage die du dir jedoch noch stellen musst, wie du es in der Datenbank dann halten möchtest.
Du kannst da entweder dann für jede Klasse eigene Tabellen machen oder eine große Tabelle, die dann aber viele null Werte hat oder man erweitert dann die Tabellen über Verknüpfungen (Joins)
Vererbung und Datenbanktabellen - da gibt es keine gute Lösung. Ich bleibe bei eine Tabelle = eine Klasse, wenn Spalten mehrfach vorkommen (ID, date_created, etc.) dann steck ich das in ein Interface und lass es gut sein.
“Voneinander erben, um Codedopplungen zu sparen” klingt gefährlich. Vererbung ist nicht dafür gedacht, “irgendeine Funktionalität praktisch und bequem in eine Klasse rein-zu-erben”. Sie sollte stattdessen eine Manifestation einer “Ist-Ein”-Beziehung sein (genauer eigentlich: “Verhält-Sich-Wie”). Auf Basis der (dünnen) bisherigen Beschreibung ist es schwierig, dort irgendwelche konreten Hinweise zu geben. Und thematisch (mit “Primary Key” usw.) klingt das so Datenbank-Nah, dass ICH sicher am allerwenigsten konkrete Hinweise geben sollte (habe von DBs keinen Plan, und daher auch nicht von Best Practices was Schemamodellierung, ORM, DAOs & Co angeht…). Aber wenn mit “voneinander erben” gemeint war, dass es ggf. verschiedene Interfaces gibt, und ggf. (nicht-öffentliche) Abstrake Basisklassen, dann ist das was anderes. Vielleich kann noch jemand was fundierteres dazu sagen.
Object ist das allgemeine Beispiel der Basisklasse von allem,
und für die besondere Klassenmende der DB-Entitäten kann es nicht schaden, eine eigene Basisklasse mit grundsätzlicher Funktionalität zu haben
die Bedingungen sind nicht strapaziert, ein String ‚ist ein‘ Object, und verhält sich auch wie ein Object hinsichtlich hashCode zu haben, equal sein zu können usw.,
eine Entität Person wäre/ würde sich wie die Basisklasse DBEntitaet verhalten, indem es Id hat usw.,
mehrere Stufen an Basisklassen nur für einzelne Attribute klingt etwas anstrengend,
selbst unter der Idealbedingung, dass es keine Klasse mit Löschdatum aber ohne Änderungsdatum gibt,
eine Basisklasse könnte das alles übernehmen, manche nicht verwendete Attribute liefern dann null usw.,
mag Probleme machen oder auch nicht, zu entscheiden
wie alles in DB-Frameworks den Regeln entspricht, beeinflusst sicher die Möglichkeiten,
manche könnten mit so einer Verteilung der Attribute und getter/ setter auf mehrere Klasse nicht klarkommen,
aber auch ich spreche da ohne allzu viel Genauigkeit, vielleicht nur Erinnerung aus früheren Zeiten
besonders das von mir Angesprochende mit nur teilweise genutzten Attributen mag gegen den unsäglichen Einsatz von Annotations sprechen:
ein in der Basisklasse annotiertes Attribut dürfte, wenn es überhaupt funktioniert, in JEDER Subklasse Verbindung zu DB erzwingen
aber die Java-Klassen gehören sowieso DB-unabhängig sauber frei programmiert,
Mappings in separate XML-Dateien, unterschiedliche Mappings für 3 verschiedene DB-Frameworks in 3 XML, alles störungsfrei nebeneinander
→ falls bisher Annotations eingesetzt kann das freilich die Arbeit/ den Schreibaufwand stark erhöhen, nicht gerade das ursprüngliche Ziel
Stufen-Basisklassen mögen hier noch entschärft möglich sein
Das wäre für mich kein Grund. Klar kann man das machen, aber du verbaust dir dadurch evtl. Flexibilität falls du irgendwann dann doch mal etwas ändern musst. Der Mehrwert ist für mich minimal.
[quote=Roy;117683]Ich habe sehr viele Entitäten die über Attribute zum speichern des Erzeugers mit Datum haben
Etwas weniger als die vorherigen Entitäten besitzen auch noch Attribute die Auskunft über das letzte Änderungsdatum geben
Dann gibt es einige Entitäten die zusätzlich noch ein Löschdatum - Attribute verfügen etc. etc. etc.[/quote]
Getreu dem Motto „composition over inheritance“ wären evtl. Embeddables die bessere Lösung.
Neben Id will man meist noch ein Version-Feld haben. Die beiden Felder plus Getter und (protected) Setter kann man schon in eine (abstrakte) Basisklasse stecken. Das ID-Feld kann dann noch gerne generisch sein. So erhält man sich die Flexibilität, auch mal was anderes als Long für Primary Key zu verwenden. Bis zu diesem Grad macht Generalisierung auch im Handling Sinn. Im (generischen) DAO hat man dann findById, findAll, …
Darüber hinaus würde ich nur für’s Felder sparen nicht generalisieren. Da bekommt man viel zu schnell eine unangenehm tiefe Vererbungshierarhie. Hier dann eher Composition (soweit es bei Entities eben geht).
Und für’s Mapping Table per Class.
Wenn es FACHLICH Sinn macht im Sinne einer echten ist-ein-Beziehung kann Vererbung gerne verwendet werden, je nach Anwendungsfall sind dann auch andere Mappings als Table per Class denkbar.
Ich habe in einigen Projekten den Ansatz gesehen. Da wurde alles, was sich generalisieren lässt, in einer generischen DAO-Basisklasse gemacht und für jede Entität gab es eine Ableitung.
In der Domain-Modell-Denkweise halte ich das für falsch. Es gibt ein paar „Wurzel-Entitäten“ diese haben ein DAO, alles andere erreicht man dann durch Navigation von diesen Etitäten aus.
Aber auch für die Wurzel-Entitäten finde ich ein generisches Basis-DAO nicht besonders gut, denn die Gefahr ist, dass man Methoden für Entitäten bereitstellt, die weder technisch noch fachlich gewollt sind.
findById wäre so ziemlich die einzige Methode die ich mir generisch vorstellen könnte. Schon bei findAll sehe ich das kritisch. Sowas kann schnell zu Performance-Problemen führen. Sobald es so eine Methode gibt, ruft sie irgendjemand, ohne groß nachzudenken, auch auf.
Sorry wenn ich da falsch Verstanden wurde, meine Intention war, das man wie ja schon erwähnt eine Abstrakte Classe machen kann, die die ID, Versionsinformationen, Änderungen und Co hat (also die technischen Informationen) die alle Entitäten benötigen und von der Erben dann die Fachlichen Objekte (wieder Verwendung/Code Dopplung ersparen) Nur einmal findById,… Da macht dann Table per Class Sinn.
Desweiteren, wenn es fachlich Sinn macht, kann man auch gerne für Datenbankobjekte eine Vererbung einbauen.
Dieses haben wir, wie von mir geschildert im Projekt gemacht und da macht auch eine Vererbung mittels Join Sinn, aber kann auch je nachdem wie viele Unterschiede es in den Spezialisierten Classen gibt, auch eine Table Sinn machen. Das muss man der Situation abhängig entscheiden.
welche Art Embeddable wären das denn?
wenn ein großes mit vor allem Id, dann in quasi jeder Klasse benötigt, auch dann noch gegenüber Basisklasse bevorzugt?
wenn mehrere einzelne, z.B. eins nur für Änderungsdatum,
wäre es dann nicht einfacher und viel bessere Composition, schlicht beim Data-Attribut zu bleiben, was ist gewonnen?
bleiben natürlich noch die Zwischenfälle, ‘zum Speichern des Erzeugers mit Datum’ als zwei Attribute, ein Embeddable dann knapp 50% gespart, naja,
dafür auch noch Aufrufe und DB-Anfragen evtl. etwas komplizierter, “select x.embY.dataZ from Klasse X x”
alle hier genannten Attribute, Dati, Erzeuger (wenn anscheinend schon oft verwendet, sonst etwas exotisch, aber kann man allgemein machen), Id, Version (nicht im ersten Posting genannt)
sind so allgemein, dass man sie jeder DB-Entity beimischen kann
generische Id mit Angabe in jeder Klasse wäre allerdings etwas anstrengend, Einigung auf Integer
(oder eher Long!, nicht nur an maximale Anzahl gleichzeitig in Gebrauch denken, auch an zeitliches Fortschreiten, auch dieselbe eine Sequence für viele Tabellen gleichzeitig nutzen, DB-weit eindeutige Id, evtl. Wunsch unterschiedlicher Id-Bereiche verschiedener DBs, irgendwann freilich auch Long überstrapaziert…)
würde den Aufwand ein ganzes Stück eher lohnen
ein großes Embeddable mit vielen wichtigen Daten, in jeder Klasse = allen Entitäten benötigt, da hast du doch zwei Drittel meines Satzes schon wiederholt,
und offensichtlich immer noch gegenüber Basisklasse vorgeschlagen, das war meine Frage dazu, so ist also der Standpunkt, ok
jede betroffene DB-Anfrage als "select x.common.id from x " oder ähnlich zu formulieren,
jede Auswertung mit Id in Java if (list.get(i).getCommon().getId().equals(searchId)) usw.,
das wäre ja ein starkes Stück…, aber denkbar,
oder bessere Aussichten durch bestimmte Anwendungskonzepte dazu?
mit Basisklasse keine realistischen Nachteile, einfachere Klassendefinitionen, unsichtbar natürliche Normalität in allen Anwendungen:
"select x.id from x " + if (list.get(i).getId().equals(searchId))
Es war kein vollständiger Satz, deshalb habe ich ihn nicht verstanden. Da ich Komposition gegenüber Vererbung bevorzuge, finde ich ein großes Embeddable mit allen technischen Attributen sinnvoll.
Embeddable-Objects meint, das es auf Java-Seite ein zusätzliches Objekt gibt (mit ID, created, updated), dieses aufgrund der 1-zu-1-Beziehung des Beinhaltenden Objekts in der selben Tabelle in der Datenbank abgelegt wird.
ID, CREATED, UPDATED, …, PROPERTIES
Es gibt also keine 3 Tabellen Common, Common_Entity, Entity, sondern nur Entity.
select x.id from x bleibt also weiterhin die Query.
if (list.get(i).getCommon().getId().equals(searchId))
kann man vermeiden, wenn man die Möglichkeiten von Java 8 ausnutzt.
public interface Common {
default Long getId() {
return getCommon().getId();
}
default Date getCreated() {
return getCommon().getCreated();
}
default Date getUpdated() {
return getCommon().getUpdated();
}
default void setUpdated(Date date) {
getCommon().setUpdated(date);
}
CommonData getCommon();
}```
```import java.util.Date;
public class CommonData {
private final Long id;
private final Date created;
private Date updated;
public CommonData(Long id, Date created) {
this.id = id;
this.created = created;
}
public Long getId() {
return id;
}
public Date getCreated() {
return created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
}```
```public class Entity implements Common {
private CommonData commonData;
@Override
public CommonData getCommon() {
return commonData;
}
public static void main(String[] args) {
Entity e = new Entity();
e.getId();
e.getUpdated();
e.getCreated();
}
}```
die Default-Methoden sind nette Sache, wobei dann nicht mehr viel zu einer Basisklasse fehlt
(und nebenbei gegenüber Basisklasse alles unnötig verdoppelt wird, 2x aber immerhin noch besser als 30x)
bei der Query sicher?
ich meine nicht SQL, sondern HQL oder wie immer es je nach Framework heißt (*), Querys mit den Entities in Java,
gar nicht so leicht, hinsichtlich Embedded Beispiele im Internet zu finden, hier (ohne Datum) Use getter/setter with HQL query? - TecHub
sind es gleich zwei Embedded ineinander, nur die erste Stufe betrachtet, dazu heißt es SELECT c FROM Customer c WHERE c.address.zip :=zip
wäre auch auf irgendeine Art SELECT c FROM Customer c WHERE c.zip :=zip
oder ohne Betrachtung des eh komplizierten Zips zumindest SELECT c FROM Customer c WHERE c.street :=street möglich?
gar auch über Nutzung der Default-Methoden? wenn daraus sinnvolles SQL für eine Query formuliert werden kann, dann stark gemacht…,
nach meiner Kenntnis geht es nur über direkt gemappte Attribute
was passiert wenn zwei Embedded gleichnamige Attribute haben?
kann man auf unterschiedliche DB-Felder mappen, besonders günstig mit XML separat,
aber wie in der HQL-Query ansprechen, wenn nicht mit Navigation?
Valider Einwand. Hatte da nie groß drüber nachgedacht, weil’s in vielen Fällen sehr bequem ist, so eine Methode zu haben (z.B. Dropdownbox/Radiobuttons für Auswahl von Geschlecht, Rollen, Typen o.ä.). Aber Du hast schon Recht, dann hat man sie für ALLE Entitäten auch die, wo man nie findAll machen würde.
Dennoch gibt es mehr als nur findById. Denke da an die verschiedenen Ausprägungen von speichern (neu anlegen vs. updaten), findByExample.
public interface Common {
default Long getId() {
return getCommon().getId();
}
default Date getCreated() {
return getCommon().getCreated();
}
default Date getUpdated() {
return getCommon().getUpdated();
}
default void setUpdated(Date date) {
getCommon().setUpdated(date);
}
CommonData getCommon();
}
Wozu diese Verrenkungen mit den Default-Methoden und dem Embedded Object? Warum nicht gleich ein reines Interface?
Einziger Grund der mir einfällt: man muss dann in den Entity-Klasse nicht mehr alle Felder wiederholen - aber das ist läppisch: Fehler würden auf der Java-Seite mit dem Interface und auf der SQL-Seite durch das Table-Mapping ja sowieso erkannt? Und wer schreibt schon alle seine Entity-Klassen von Hand?