Wie lasst ihr eure IDs generieren?

M

maki

Gast
Hallo zusammen,

habe aus Interesse ein paar Fragen an euch bez. JPA bzw. EclipseLink/Hibernate/anderer ORMs:
- Wie lasst ihr die IDs eurer Entitäten erzeugen?
- Wie implementiert ihr die equals und hashCode Methoden eurer Entitäten?

Bin gerade an einem Projekt(u.a. zu Übungszwecken), bei dem ich versuche mich an DDD zu halten, also "Domain Driven Design".

In Eric Evans Buch dazu werden ja die Bausteine dazu erklärt, Entites, ValueObjects, Repositories, Factories, etc. pp.

U.a. erklärt Evans, dass Entities Ids brauchen und nur danach verglichen werden sollten/dürfen, schon bei der Erzeugung bekommen Entities Ids, komplexe Objekte wie Entities werden in Factories erzeugt, diese müssen wissen wie man Ids vergibt.

Habe bis jetzt nur Vorteile darin gesehen (abgesehen davon wie man der Factory beibringt, welche Ids sie vergeben kann), Entitäten, die nur anhand ihrer Id identifiziert werden, nutzen die Id für equals und hashCode, die Id ändert sich nie(egal ob vor oder nach dem persistieren), dadurch kann man problemlos auch transiente (also nicht persistierte) Entitäten miteinander vergleichen, sie in HashSet verwenden, TreeSets, etc. pp.

Probleme die dabei vermieden werden:
- equals & hashCode können wirklich "sauber" imlpementiert werden, ohne Kompromisse, keine bösen Überraschungen mit HashSet, TreeSets oder sonstigen Collections.
- wie die Datenbank bzw. das ORM Ids verteilt (Sequence, Identity, etc. pp.) ist vollkommen egal, weil die Db schlicht keine Ids mehr selber vergibt, man ist unabhängig

Probleme die man bekommt:
- Man muss selber einen Weg suchen, welche Ids vergeben werden können, UUIDs sind eine recht einfache Möglichkeit, sind im Bezug auf Performance aber nicht trivial, eine Oracle Db zB. kann man dazu bringen, Sequenzen zu reservieren und dann diese zu verwenden, im Moment mache ich das so: Beim initialisieren der Factory wird ein Id Generator injiziert (Ids sind Integers), der wird mit der höchsten bereits vorhandenen Id der Tabelle initialisiert und zählt von da an nur nach oben.
- Es kann schwieriger werden, daten "hintenrum" per SQL Script in die Db zu schiessen während die App noch läuft, aber das ist ja kein neues Problem bei ORMs.

Wollte mal eure Meinung dazu hören und was eure Best practices sind :)
 
M

maki

Gast
Hi SirWayne,

wie gehst du mit den Einschränkungen um, die sich daraus ergeben, dass sich die Id ändert (transient vs. persistent) bzw. wie implementierst du equals (& hashCode)?

Das mit der Schnittstelle hört sich interessant an, was steckt dahinter?
 
G

Gast2

Gast
wie gehst du mit den Einschränkungen um, die sich daraus ergeben, dass sich die Id ändert (transient vs. persistent) bzw. wie implementierst du equals (& hashCode)?

Ich seh grad dass die bei uns gar nicht überschrieben sind (ohje^^).

Ich weiß nicht genau was du damit meinst, dass sich die ID ändert. Das Objekt wird ohne ID angelegt wenn es in die DB kommt, wird eine ID vergeben. Danach ändert sich die ID ja nicht mehr.


Das mit der Schnittstelle hört sich interessant an, was steckt dahinter?

Damit meine ich nur dass manche id's von außen in die Applikationen kommen(GUI, Webservice usw.).
 
M

maki

Gast
Ich benutze einen Ansatz aufbauend auf dem hier beschriebenen: Object Generation: A Better Approach to Hibernate Integration

Ist halt Hibernate spezifisch; dafür kann man die Generatoren weiter benutzen.
Interessant hexx.

Da wird die Id auch schon bei der Erzeugung des Objektes vergeben, u.U. ist dafür eine DB Abfrage nötig.
Persistenz Code lesen ist immer witzig imho, man findet einiges wieder, das 1:1 übereinstimmt, zB. getEntityName, equals & hashCode werden in der Entity Basis Klasse implementiert u.a.

Vielleciht täusche ich mich, aber hätte man da nicht einiges am Komplexität (Javaassist etc.) verzichten können falls eine Factory genutzt würde?
Oder liegt das daran, das Hibernate wieder selber in der Lage sein soll zu unterscheiden ob ein INSERT oder ein UPDATE nötig ist?

Ich seh grad dass die bei uns gar nicht überschrieben sind (ohje^^).

Ich weiß nicht genau was du damit meinst, dass sich die ID ändert. Das Objekt wird ohne ID angelegt wenn es in die DB kommt, wird eine ID vergeben. Danach ändert sich die ID ja nicht mehr.
Das ist ja genau das Problem, ehemals transiente Entitäten haben nach dem persistieren eine andere ID.
Wenn man diese trans. Entitäten in die UI schickt, wird es schwer sie miteinander zuvergleichen, falls man sie in Collections steckt, ist es fraglich ob man sie wiederbekommt (HashSet, HashMap, etc. pp.) wenn sie danach persistiert wurden.
Fand das immer "unschön" da equals & hashCode eben doch sehr elementare Methoden sind die oft vorrausgesetzt werden.

Evans sagt, dass Entiäten immer eine Id brauchen, unabhängig davon ob sie schon persistiert wurden oder nicht, bis jetzt sieht es für mich so aus, als ob das Goldrichtig ist.

Damit meine ich nur dass manche id's von außen in die Applikationen kommen(GUI, Webservice usw.).
Nur in der Form von Fremdschlüsseln, oder auch PKs?
 
Zuletzt bearbeitet von einem Moderator:

Noctarius

Top Contributor
Also die "einfache" Version ist: Entity persistieren bevor du es weiterbenutzt *duck*

Aber Spaß beiseite, wenn es auf die ID ankommt habe ich dies tatsächlich meistens gemacht (nur um sicher zu gehen).
 
G

Gast2

Gast
Nur in Form von Fremdschlüsseln, oder auch PKs?

In Form von PKs

Also die "einfache" Version ist: Entity persistieren bevor du es weiterbenutzt *duck*

Aber Spaß beiseite, wenn es auf die ID ankommt habe ich dies tatsächlich meistens gemacht (nur um sicher zu gehen).

Ja so kenne ich es bis jetzt auch. Ich kenne den Fall nicht, dass das transiente Objekt eine andere ID als das persistente hat.

Objekt instanzieren ohne ID--> Objekt persistieren --> Objekt hat ID-->weiterbenutzen

In unserem Anwendungsfall funktioniert das wunderbar.
 
Zuletzt bearbeitet von einem Moderator:
G

Gelöschtes Mitglied 5909

Gast
Bisher habe ich ehrlich gesagt Quasi immer nur mit Persistenten Objekten gearbeitet.

Seit ich angefangen habe auch NoSQL DBs zu verwenden, sind für mich diese klassischen Ids eher technischer Natur,
sie sollten nicht für den Anwender Sichtbar, geschweige denn Editierbar sein.

Aus meiner Sicht gibt verschiedene Möglichkeiten:

1. Du verwendest UUID oder etwas ähnliches
- das macht z.B. MongoDB so (kein java.util.UUID, aber ähnlich)

2. Du machst das mit den Factories und rufst die Sequence selber auf.

3. Ich bin kein DDD spezialist, aber welche Vorteile genau hast du mit dem Id Ansatz?
Ist das was du vorhast nicht sehr stark an eine Datenbank gekoppelt?
Für mich hört sich das eher so an, als sollte alles andere außer die Ids ind equals/hashcode.
Es sei denn du mehrere exakt Identische Datensätze und das ist auch so gewünscht.
Für mich war DDD bisher Datenorientiert, aber nicht Datenbankorientiert. Das ist ein feiner unterschied.

4. Du verwendest eine technische und eine logische id. Die technische lässt du von der DB generieren,
die logische verwaltest du selber (z.B. UUID). Die technische Id wird aber nur für die DB verwendet und ansonsten Ignoriert.

Was ich mich noch Frage: Welche Probleme erhoffst du dir zu lösen? Welche Probleme hast du denn mit Sets etc.,
wenn du die Id weglässt?
 
M

maki

Gast
@Noctarius & SirWayne
Kenne dieses vorgehen auch, Entitäten grundsätzlich zu persistieren, ist aber für eine App mit GUI nicht immer der beste Weg imho, falls der Anwender zB. eine neue Entität anlegen will und es sich dann doch anders überlegt. von der Performance und Logik ist es auch nicht ideal, Entitäten zu speichern, bevor diese sinnvolle Werte durch den User bekommen haben.
Es geht, ist imho aber etwas unschön, da es direkte Abhängigkeiten zur Persistenz (und deren Implementierung) gibt, denn ohne sind meine Domainobejkte u.U. nicht gültig.

@raiL
Seit ich angefangen habe auch NoSQL DBs zu verwenden, sind für mich diese klassischen Ids eher technischer Natur,
sie sollten nicht für den Anwender Sichtbar, geschweige denn Editierbar sein.
Das ist bei meinem und hexx Ansatz auch so, id sind rein technisch, aber fachlich muss jede Entität eine ID haben, den Entitäten im DDD Sinne haben immer sowas wie eine Id als Objektidentität, und letzteres ist eine Grundvorraussetzung für Entitäten.

Aus meiner Sicht gibt verschiedene Möglichkeiten:

1. Du verwendest UUID oder etwas ähnliches
- das macht z.B. MongoDB so (kein java.util.UUID, aber ähnlich)
Es muss keine UUID sein, ein Integer tut es auch und ist in den meisten RDBMS performanter.

2. Du machst das mit den Factories und rufst die Sequence selber auf.
Klar, Factories haben auch andere Vorteile fürs Modell zB., eine Sequence kann aus der DB kommen (Oracle zB.), muss aber nciht.

3. Ich bin kein DDD spezialist, aber welche Vorteile genau hast du mit dem Id Ansatz?
Ist das was du vorhast nicht sehr stark an eine Datenbank gekoppelt?
Für mich hört sich das eher so an, als sollte alles andere außer die Ids ind equals/hashcode.
Es sei denn du mehrere exakt Identische Datensätze und das ist auch so gewünscht.
Für mich war DDD bisher Datenorientiert, aber nicht Datenbankorientiert. Das ist ein feiner unterschied.
Das Gegenteil ist der Fall, mit diesem Id Ansatz bin ich in der Domäne unabhängig von der Db, anders als bei den anderen Ansätzen, ob diese Sequenzen oder Identity unterstützt, ich bin sogar unabhängig davon, ob die Objekte schon persistent sind, Entitäten haben eine Id, immer, unabhängig davon ob sie schon persistiert wurden.

Wie gesagt, Entitäten haben eine Id in DDD, die muss nur eindeutig sein, sonst nix.
Sie dürfen nur anhand dieser Id gleich sein(Objektidentität).
Beispiel: Es gibt viele Hans Müller, weder der Name, noch das Geburtsdatum sind eindeutig, eine Kombination auch nicht. Menschen haben eine Identität, die unabhängig vom Namen, Alter, und Geschlecht ist, das wird mit der Id ausgedrückt.

DDD definiert aber nicht nur Entitäten (also Objekte mit Identität), sondern auch ValueObjects, diese haben keine Identität, sondern werden auschliesslich anhand ihrer Werte verglichen, und sind Immutable(!). ValueObjects sind in JPA übrigens meist Kandidaten für Embeddables, die brauchen keine eigene Id, sondern gehören immer zu einer Entität.
Beispiel: Es kann dir egal sein welchen 20 Euro Schein du hast, die sind alle gleich, keine Identität, nur Wert.

DDD ist übrigens weder Daten- noch Datenbankorientiert, sondern "Domänenorientiert", d.h. die Problemdomäne und das Model dieser stehen im Mittelpunkt.

4. Du verwendest eine technische und eine logische id. Die technische lässt du von der DB generieren,
die logische verwaltest du selber (z.B. UUID). Die technische Id wird aber nur für die DB verwendet und ansonsten Ignoriert.
2 Ids? Sehe darin keine Vorteile ehrlich gesagt.

Was ich mich noch Frage: Welche Probleme erhoffst du dir zu lösen? Welche Probleme hast du denn mit Sets etc.,
wenn du die Id weglässt?
equals & hashCode haben einen recht strikten Vertrag einzuhalten, u.a. dass sie sich nie ändern dürfen wenn man vorhat HashSet, TreeSet, HashMap etc. zu verwenden, sonst sind sie schlicht nicht mehr auffindbar nach der Änderung bzw. man bekommt ein unvorhergesehenes verhalten.

Das ist der Grund warum hier auch andere schreiben, dass sie die Objekte immer persistieren vor der Verwendung, auch wenn das nicht immer nötig wäre wegen des Use Cases oder sogar umständlich (User will neuen Datensatz anlegen, überlegt es sich dann doch anderes und drückt cancel -> nun muss die App die Entität wieder löschen, obwohl der User nie "speichern" gedrückt hatte).

Kann kaum glauben dass du damit noch nie Probleme hattest, kommt natürlich immer darauf an, wie man es nutzt.
 
G

Gast2

Gast
@Noctarius & SirWayne
Kenne dieses vorgehen auch, Entitäten grundsätzlich zu persistieren, ist aber für eine App mit GUI nicht immer der beste Weg imho, falls der Anwender zB. eine neue Entität anlegen will und es sich dann doch anders überlegt.

Dann wird auf z.b. auf "cancel" gedrückt und das Objekt kommt erst gar nicht in die DB und bekommt auch keine ID.

Mal eine Frage zum grundsätzlichen Verständnis, angenommen du hast 2 Klassen A und B und mehrere Objekte von davon.

Müssen dann alle Objekte eine eindeutige ID haben
z.b.
Objekt A1 Id: 1
Objekt B1 Id: 2
Objekt A2 Id: 3
Objekt B2 Id: 4
usw.

Oder jedes Objekte muss eindeutig in seiner Instanzklasse sein?
z.b.
Objekt A1 Id: 1
Objekt A2 Id: 2

Objekt B1 Id: 1
Objekt B2 Id: 2
 
M

maki

Gast
Dann wird auf z.b. auf "cancel" gedrückt und das Objekt kommt erst gar nicht in die DB und bekommt auch keine ID.
Dann musst du aber mit Objekten arbeiten, die noch keine Id haben, dass ist nicht das was ich in deinem vorherigen post verstanden habe, also das Entitäten immer erst Persistiert werden vor dem verwenden.
Wenn du nun diese Id in equals/hashCode nutzt, werden diese Methoden sich anders verhalten, je nachdem, ob die Objekte schon persitiert wurden oder nicht.

Mal eine Frage zum grundsätzlichen Verständnis, angenommen du hast 2 Klassen A und B und mehrere Objekte von davon.

Müssen dann alle Objekte eine eindeutige ID haben
z.b.
Objekt A1 Id: 1
Objekt B1 Id: 2
Objekt A2 Id: 3
Objekt B2 Id: 4
usw.

Oder jedes Objekte muss eindeutig in seiner Instanzklasse sein?
z.b.
Objekt A1 Id: 1
Objekt A2 Id: 2

Objekt B1 Id: 1
Objekt B2 Id: 2
Das ist ein technischer Aspekt imho, ob Objekte Klassenübergreifend eindeutig sind.
Beides geht.
Man kann seine Id Generatoren so programmieren/konfigurieren, dass wirklich jedes Objekt eine eindeutige Id unabhängig von der Klasse bekommt (UUID oder Integer/Long), oder dass Objekte derselben Klasse eben nur untereinander eindeutige Ids haben.

Wichtig ist in DDD wie gesagt nur, das Entitäten anhand einer Id unterschieden werden, also Identität haben, die unabhängig von anderen Werten ist, und diese Id von Anfang an gegeben ist und sich während des Lifecycles nicht ändert.
Das passt sehr gut zu den Anforderungen von equals/hashCode :)
 
G

Gast2

Gast
Dann musst du aber mit Objekten arbeiten, die noch keine Id haben, dass ist nicht das was ich in deinem vorherigen post verstanden habe, also das Entitäten immer erst Persistiert werden vor dem verwenden.
Wenn du nun diese Id in equals/hashCode nutzt, werden diese Methoden sich anders verhalten, je nachdem, ob die Objekte schon persitiert wurden oder nicht.
Wie gesagt so einen Anwendungsfall hatte ich noch nicht. Das Objekt muss angelegt sein damit der Worklflow weiter geht. z.B. ein Formular um eine Person anzulegen. Der Benutzer gibt alle Daten ein und drückt anschließend speichern oder cancel. Nach speichern kann das Personenobjekt weiter verwendet werden. Nach cancel stirbt das Objekt.

Wenn du nun diese Id in equals/hashCode nutzt, werden diese Methoden sich anders verhalten, je nachdem, ob die Objekte schon persitiert wurden oder nicht.
Ja das ist schon logisch, aber wie gesagt werden die Objekte erst weiter verwendet wenn sie in der DB sind. Aber ich verstehe was du meinst.

Das ist ein technischer Aspekt imho, ob Objekte Klassenübergreifend eindeutig sind.
Beides geht.
Man kann seine Id Generatoren so programmieren/konfigurieren, dass wirklich jedes Objekt eine eindeutige Id unabhängig von der Klasse bekommt (UUID oder Integer/Long), oder dass Objekte derselben Klasse eben nur untereinander eindeutige Ids haben.

Variante 2 würde ich besser finden, weil sonst hat man in der DB für jeden Eintrag eine neue ID
 
M

maki

Gast
Wie gesagt so einen Anwendungsfall hatte ich noch nicht. Das Objekt muss angelegt sein damit der Worklflow weiter geht. z.B. ein Formular um eine Person anzulegen. Der Benutzer gibt alle Daten ein und drückt anschließend speichern oder cancel. Nach speichern kann das Personenobjekt weiter verwendet werden. Nach cancel stirbt das Objekt.

Ja das ist schon logisch, aber wie gesagt werden die Objekte erst weiter verwendet wenn sie in der DB sind. Aber ich verstehe was du meinst.
Das funktioniert ganz ok solange es sich nur um eine einzelne neue Entität handelt (nicht mehrere) und man eben darauf verzichtet, transiente Entitäten in HashSets, TreeSets und HashMaps zu stecken, allesamt Bestandteil der Collection API (einem Teil der Java Standard API!).
Auch ist es problematisch, wenn man eine längere Transaktion drumherum haben will, in der erst eine neue Entität und dann weitere neue abhängige Entitäten in einem Rutsch oder gar nicht in der DB haben will.

Das sind Einschränkungen die man kennen muss imho, wenn man die ID von der Persistenzschicht vergeben lassen will und man darauf die equals/hashCode Methoden basieren lässt.

Ich hab es zB. schon öfters erlebt, dass in Hibernate/EclipseLink/JPA Projekten anstatt einem Set eine List nimmt, obwohl die List Elemente nur einmal aufnehmen darf, aber mit Set (HashSet) riskiert man eben dass es u.U. schiefgeht.
 
G

Gast2

Gast
Das funktioniert ganz ok solange es sich nur um eine einzelne neue Entität handelt (nicht mehrere) und man eben darauf verzichtet, transiente Entitäten in HashSets, TreeSets und HashMaps zu stecken, allesamt Bestandteil der Collection API (einem Teil der Java Standard API!).
Auch ist es problematisch, wenn man eine längere Transaktion drumherum haben will, in der erst eine neue Entität und dann weitere neue abhängige Entitäten in einem Rutsch oder gar nicht in der DB haben will.

Man kann mit transienten Objekten arbeiten, doch sobald sie in die DB kommen musst man mit dieser weiterarbeiten. Wie gesagt ich hatte noch keinen Anwendungsfall wo es zu Problemen kam.

Kennst du ein UseCase welcher Probleme machen würde?
 
M

maki

Gast
Das hab ich doch gerade, oder?

Mehrere transiente von einander abhängige Entitäten, alle in einer einzigen Transaktion anlegen.

Nachtrag:
Hatte das Problem selber schon öfters in Projekten, eigentlich allen ORM Projekten die nciht ganz trivial waren.

Der Nutzer kann zu einem Document (Entität) "FileAttachments" (auch eine Entität) hochladen, mehrere auf einmal. Das Document gab es vorher schon (persistent).
Wenn man jetzt die Attachments vor dem persistieren, genaugenommen beim Transport zurück von der UI Schicht in die Businessschicht in ein Set wirft, hat das Set immer die größe 1 *g*
Hatte das Dokument vorher schon Attachments wird es lustig beim Merge ;)

DDD definiert Entitäten als Objekte mit Identität, unabhängig von der Persistenzlösung, damit vermeidet man solche Probleme, die Diskussion wie und wann man Entitäten Ids gibt ist übrigens nicht so neu.
 
Zuletzt bearbeitet von einem Moderator:
G

Gast2

Gast
Das hab ich doch gerade, oder?

Mehrere transiente von einander abhängige Entitäten, alle in einer einzigen Transaktion anlegen.
Sobald sie dann persistiert werden bekommen sie eine id.
z.B. eine Person hat mehrere Verträge
Legt man die Person an und addet von mir aus 3 neue Veträge. Persisitiert diese.

Ich glaub ich hab das Problem noch nicht erkannt. Ich seh dein Ziel und versteh es auch, aber dort wo ein Problem auftreten könnte habe ich noch nicht gesehen.
 
M

maki

Gast
Sobald sie dann persistiert werden bekommen sie eine id.
z.B. eine Person hat mehrere Verträge
Legt man die Person an und addet von mir aus 3 neue Verträge. Persisitiert diese.
Das Problem ist eben, dass die Id abhängig ist, ob das Objekt schon persistent ist.
Siehe mein Beispiel von oben.
Kann eine Person denselben Vertrag mehrmals haben? Bestimmt nicht ;)
Darfst du dann ein Set darauf machen? Bestimmt nicht.
Wie willst du das sicherstellen? Mit fachlichen Schlüsseln wieder? Pfui...

Ich glaub ich hab das Problem noch nicht erkannt. Ich seh dein Ziel und versteh es auch, aber dort wo ein Problem auftreten könnte habe ich noch nicht gesehen.
Das kommt schon noch.
Bin ja nicht der einzige mit diesem Problem, wie gesagt, Diskussion zu diesem Thema sind nicht so neu ;)
 
G

Gelöschtes Mitglied 5909

Gast
Ich muss maki insgesamt recht geben.

Warum ich bisher keine Probleme hatte war warscheinlich dass ich nie Transiente Objekte in Sets etc. verwendet habe.
Das braucht man aber warscheinlich nur, wenn man bulk inserts machen will.

Bei mir war es bisher so, dass ein user entweder ein neues objekt eingefügt hat, wodurch es dann neu geladen wurde (z.b. für list view).
Oder aber er hat abbrechen geklickt und dann wurde es halt nicht gespeichert und auch das transiente objekt entfernt.
 
G

Gast2

Gast
Ich muss maki insgesamt recht geben.

Warum ich bisher keine Probleme hatte war warscheinlich dass ich nie Transiente Objekte in Sets etc. verwendet habe.
Das braucht man aber warscheinlich nur, wenn man bulk inserts machen will.

Bei mir war es bisher so, dass ein user entweder ein neues objekt eingefügt hat, wodurch es dann neu geladen wurde (z.b. für list view).
Oder aber er hat abbrechen geklickt und dann wurde es halt nicht gespeichert und auch das transiente objekt entfernt.
Das Problem bekommt man auch, wenn man eine "lange" Transaktion hat, in der mehrere transiente Objekte derselben Klasse angelegt werden sollen, oder auch nicht.
Das mit "keine Sets verwenden" kommt mir bekannt vor ;)

Kann eine Person denselben Vertrag mehrmals haben? Bestimmt nicht ;)
Darfst du dann ein Set darauf machen? Bestimmt nicht.
Mhm... Hier hänge ich nun ich verstehe nicht wie der Vertrag mehrmal hinzugefügt werden sollte.
Die neuen Verträge sind transient.
Wirfst du sie in ein Set, hast du danach nur noch einen transienten Vertrag, denn alle transienten Entitäten einer Klasse sind identisch (id == null) ;)
Mit einem Set könntest du im Code bzw. schon einem interface/methoden Sign. klarmachen, dass die Objekte nur einmalig vorhanden sind, das kannst du aber nicht, weil die Entitäten den equals/hashCode Vertrag nicht einhalten.

Nebenbei:
ich sage ja nciht dass es nciht gehen würde mit Db generierten Ids die erst nach dem persistieren vorhanden sind.
Das geht, hat aber einige Einschränkungen & Nachteile, die man vermeidet, wenn man eben nicht die DB Ids vergeben lässt.

EDIT: Maki ich glaub du hast unsere Einträge falsch editiert ;)
 
Zuletzt bearbeitet von einem Moderator:
G

Gelöschtes Mitglied 5909

Gast
Was mich interessieren würde:

wie willst du losgelöst von der DB Integer/Long ids generieren und deren Eindeutigkeit garantieren?
Insbesondere auch, wenn ggf. die Anwendung neu gestartet wird. Mit nem AtomicLong ist das nicht getan.
Und System.currentTimeMilis() bzw. random() ist auch so ne Sache...

// Edit

was ich mich noch frage: verwendet der ein oder andere ORMapper eigentlich equals()/hashCode() um zu guggen,
ob überhaupt was gemacht werden muss? Da hatte man dann ja echt probleme wenn man nur die id nimmt.
 
Zuletzt bearbeitet von einem Moderator:
G

Gast2

Gast
Was mich interessieren würde:

wie willst du losgelöst von der DB Integer/Long ids generieren und deren Eindeutigkeit garantieren?
Insbesondere auch, wenn ggf. die Anwendung neu gestartet wird. Mit nem AtomicLong ist das nicht getan.
Und System.currentTimeMilis() bzw. random() ist auch so ne Sache...

Wäre auch interessant, wenn Objekte auf dem (Richt)Client angelegt werden.
 
M

maki

Gast
Das ist eine gute Frage raiL.

Man muss natürlich die "uniqueness" garantieren können, sonst gibt es später Exceptions.

Einfachster Weg: UUID
Problem dabei: Nicht performant bei großen Datenmengen.

Meine Lösung basierend auf DDD:
Transiente Entities werden von Factories erzeugt, diese Wissen wie Ids zu vergeben sind (das ist reines DDD bis hier).
Meine Factories sind @Singletons (guice, nix getInstance), erhalten bei der Erzeugung einen IdGenerator, der wird mit der höchsten Id aus der jeweiligen Entity Tabelle initialisiert, fertig.
Im schlimmsten Falle kann eine neue Entity nicht persistiert werden, weil die Id schon vergeben war, zB. weil jemand direkt in der DB per SQL Insert Statement Entites angelegt hatte während die App noch läuft, aber das ist auch ansosnten ein problem für ORMs.
Als DB nutze ich zurzeit H2.
Je nachdem könnte man den IdGenerator auch eine Sequenz durch eine Orable Db reservieren lassen (hat Derby das nicht auch?).

hier mla ein bisschen exemplarischer Code, reduziert auf das nötigste:

BAsisklasse für Entities:
Java:
public abstract class AbstractEntity<E extends Entity> implements Entity {

	private Integer id;

	private Integer version;
	
	@Override
	public Integer getId() {
		return id;
	}

	@Override
	public Integer getVersion() {
		return version;
	}

	protected void setId(final Integer id) {
		this.id = id;
	}

	protected abstract Class<E> getEntitytype();
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;

		if (!(getEntitytype().getClass().isInstance(obj)))				
			return false;
		
		Entity that = (Entity) obj;

		return this.getId().equals(that.getId());
	}

	@Override
	public int hashCode() {
		return getId().hashCode();
	}

}
equals & hashCode werden hier einmal für alle Entities implementiert, sind auch recht simpel ;)

Der konkrete IdGenerator
Java:
public class SequentialIdGenerator implements IdGenerator {

	private final AtomicInteger actualId = new AtomicInteger();
	
	public SequentialIdGenerator(final int initValue) {
		this.actualId.set(initValue);
	}

	@Override
	public int getNextId() {
		return actualId.incrementAndGet();
	}

	@Override
	public int getActualId() {
		return actualId.get();
	}

}

Eine konkrete Factory:
Java:
@Singleton
public class ProjectFactoryImpl implements ProjectFactory {

	private IdGenerator idGenerator;
	
	private AssignmentFactory assignmentFactory;

	@Inject
	public void setIdGenerator(final @ProjectIdGenerator IdGenerator idGenerator) {
		this.idGenerator = idGenerator;
	}

	@Inject
	public void setValueObjectFactory(final AssignmentFactory assignmentFactory) {
		this.assignmentFactory = assignmentFactory;
	}
	
	@Override
	public Project createProject(String name) {		
		return new ProjectImpl(idGenerator.getNextId(), name, assignmentFactory);
	}

}

Dann noch den IdGeneratorProvider.
Java:
public abstract class IdGeneratorProvider<E extends Entity> implements Provider<IdGenerator> {

	@Inject
	private EntityManager em;

	protected abstract String getEntityName();

	@Override
	public IdGenerator get() {
		return new SequentialIdGenerator(getHighestId());
	}

	protected int getHighestId() {
		Integer highestId = em.createQuery(" SELECT max(e.id) FROM " + getEntityName() + " e", Integer.class).
							getSingleResult();

		return (highestId == null) ? 0 : highestId;
	}

	public static class ProjectIdGeneratorProvider extends IdGeneratorProvider<Person> {

		@Override
		protected String getEntityName() {
			return "Project";
		}

	}
	
...
}
Das ganze ist imho nicht so komplex, man könnte zwar die Factory noch weiter in guice "verstecken", aber Factories sind nunmal Teil eines DDD Models, was aber auch andere Vorteile hat.

was ich mich noch frage: verwendet der ein oder andere ORMapper eigentlich equals()/hashCode() um zu guggen,
ob überhaupt was gemacht werden muss? Da hatte man dann ja echt probleme wenn man nur die id nimmt.
hibernate zB. garantiert dass in einer Session keine Entitäten equals true ergeben.
Aber ob "etwas gemacht" werden muss, wird anders festgelegt, nicht an equals, EclipseLink zB. nutzt dafür PropertyChangeListener, glaube Hibernate wird das ähnlich machen, einfach mal die Bytecode instrumentierung nutzen und dann die Klassen dekompilieren, dann sieht was sonst nur zur Laufzeit sichtbar wäre.
 
G

Gelöschtes Mitglied 5909

Gast
Mehrere sachen, die mir nicht 100% gefallen:

- max(id): da sträubt es sich bei mir irgendwie. Das geht nur weil es a) Singletons sind b) nix sonst auf der Datenbank läuft (Skripte etc.)
- das risiko, dass ein valider insert fehlschlägt erscheint mir subjektiv noch zu hoch
- Neue Entwickler müssen das wissen, sonst kanns ganz schnell knallen
- Das ist doch wieder sehr persistenz-nah. Was machst bei du einer Client Server Anwendung? Da muss strengenommen der Client die IDs vergeben.


Und dann würde mich noch interessieren, wie deine AssignmentFactory aussieht und was die genau macht.
 
M

maki

Gast
Mehrere sachen, die mir nicht 100% gefallen:

- max(id): da sträubt es sich bei mir irgendwie. Das geht nur weil es a) Singletons sind b) nix sonst auf der Datenbank läuft (Skripte etc.)
Das Problem hat man aber wie gesagt oft mit ORMs, diese haben schliesslich einen Cache und bekommen direkte Änderungen in der DB nicht unbedingt mit.

Ändere doch eine Entity direkt in der DB und schau mal ob, wann und wie dein ORM darauf kommt ;)
Die Chancen dass das ORM das gar nicht mitbekommt sind groß.

- das risiko, dass ein valider insert fehlschlägt erscheint mir subjektiv noch zu hoch
Eine Entity mit einer bereits vergebene Id kann nicht valide sein.
Das Risiko dass du umscheibst ist ja eher auf deinen ersten Punkt ausgerichtet, oder?

- Neue Entwickler müssen das wissen, sonst kanns ganz schnell knallen
Nö, der Entwickler muss gar nix wissen, ausser dass er Entities nur von Factories erzeugen lässt, sonst nix. das ist weniger als wenn er einen Konstruktor aufrufen müsste ;)
Factories sind was schönes :)
Wenn man die Id erst von der Persistenz erzeugen lässt, dann muss der Entwickler viel wissen, vor allem, was er nicht darf.

- Das ist doch wieder sehr persistenz-nah. Was machst bei du einer Client Server Anwendung? Da muss strengenommen der Client die IDs vergeben.
Nicht wirklich "persistenz-nah".
Die ganze Logik wie Ids vergeben werden, steckt im Provider und im IdGenerator, sonst nirgendwo.
Ein paar Änderungen dort, und schon kann es ganz anders laufen (Sequenzen zB.), wenn man die Id noch durch Generics parametrisiert, muss man auch nur dort ändern um zB. UUIDs zu nutzen.
Also schon sehr weit weg von der eigentlichen Id und Persistenz.
Das einzige was wirklich wichtig ist, ist das Ids eindeutig sind (zumindest pro Klasse)

Client-Server Anwendungen sind normal, aber in DDD existieren Domain Objekte nur in der Businesstier, genaugenommen in einem eigenen Layer(Domainlayer), sie enthalten viel Logik und sind keine dummen Datencontainer, deswegen geht das nicht anders.
Clients brauchen DTOs.

Und dann würde mich noch interessieren, wie deine AssignmentFactory aussieht und was die genau macht.
Assignment ist eine Entität, die AssignmentFactory erzeugt "assignments", eine Zuweisung von Personen zu Projekten mit einer "role" (String der die Rolle beschreibt) und einem Zeitraum (TimeRange als ValueObject, hat ein start Datum, ein end Datum und ein paar Methoden).

In DDD dürfen Domain Objekte mit anderen Domainobjekten kommunizieren, d.h. dass es in DDD völlig normal ist, das ein Project die ihm zugewiesenen Personen (Assignment) selber im Repository (ein repository ist ein DAO, das anders verwendet wird, nämlich von Domänenobjekten aus) suchen, anlegen etc. darf, dazu gehört das erzeugen, was aber natürlich von einer Factory übernommen wird.

Project 1-* Assignment *-1 Person

Denke dass es verwirrend ist wenn ich versuche DDD zu beschreiben und wie man Ids erzeugen kann/sollte, sorry für die Verwirrung.
 
Zuletzt bearbeitet von einem Moderator:

nocturne

Bekanntes Mitglied
Hm, ich hab nun alles durchgelesen.

Ich vermisse Stichwörter wie Enver und Auditing....

Ich arbeite permanent in Umgebungen mit long-life-transactions. Ich würde jedem im professionellen Umfeld empfehlen die fertigen long-life-transaction-Lösungen zu verwenden, selbst im short-life-transaction Umfeld.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
M Eure Meinung bitte Data Tier 1
T Hibernate/Spring JPA: eigene ID generieren Data Tier 5

Ähnliche Java Themen


Oben