Ich arbeite seit einiger Zeit mit JPA (Implementation Hibernate) und jedes Mal, wenn ich Entitäten erstellen muss, habe ich Probleme mit AccessType, unveränderlichen Eigenschaften, equals / hashCode, ....
Deshalb habe ich mich entschlossen, die allgemeinen Best Practices für jede Ausgabe herauszufinden und diese für den persönlichen Gebrauch aufzuschreiben.
Es würde mir jedoch nichts ausmachen, wenn jemand dies kommentiert oder mir sagt, wo ich falsch liege.
Entitätsklasse
Serializable implementieren
Grund: Die Spezifikation besagt, dass Sie dies tun müssen, aber einige JPA-Anbieter erzwingen dies nicht. Der Ruhezustand als JPA-Anbieter erzwingt dies nicht, kann jedoch mit ClassCastException irgendwo tief im Magen fehlschlagen, wenn Serializable nicht implementiert wurde.
Konstruktoren
Erstellen Sie einen Konstruktor mit allen erforderlichen Feldern der Entität
Grund: Ein Konstruktor sollte die erstellte Instanz immer in einem vernünftigen Zustand belassen.
Neben diesem Konstruktor: Haben Sie ein Paket privaten Standardkonstruktor
Grund: Der Standardkonstruktor ist erforderlich, damit der Ruhezustand die Entität initialisiert. privat ist zulässig, aber für die Laufzeit-Proxy-Generierung und den effizienten Datenabruf ohne Bytecode-Instrumentierung ist eine private (oder öffentliche) Paketsichtbarkeit erforderlich.
Felder / Eigenschaften
Verwenden Sie den Feldzugriff im Allgemeinen und den Eigenschaftenzugriff bei Bedarf
Grund: Dies ist wahrscheinlich das umstrittenste Problem, da es für das eine oder andere keine klaren und überzeugenden Argumente gibt (Eigenschaftszugriff vs. Feldzugriff). Der Feldzugriff scheint jedoch allgemeiner Favorit zu sein, da der Code klarer ist, die Kapselung besser ist und keine Setter für unveränderliche Felder erstellt werden müssen
Setter für unveränderliche Felder weglassen (für Zugriffstypfeld nicht erforderlich)
- Eigenschaften können privat sein
Grund: Ich habe einmal gehört, dass geschützt besser für die Leistung im Ruhezustand ist, aber alles, was ich im Web finden kann, ist: Der Ruhezustand kann direkt auf öffentliche, private und geschützte Zugriffsmethoden sowie auf öffentliche, private und geschützte Felder zugreifen . Die Wahl liegt bei Ihnen und Sie können sie an Ihr Anwendungsdesign anpassen.
Gleich / hashCode
- Verwenden Sie niemals eine generierte ID, wenn diese ID nur festgelegt wird, wenn die Entität beibehalten wird
- Nach Präferenz: Verwenden Sie unveränderliche Werte, um einen eindeutigen Geschäftsschlüssel zu bilden, und testen Sie damit die Gleichheit
- Wenn kein eindeutiger Geschäftsschlüssel verfügbar ist, verwenden Sie eine nicht vorübergehende UUID, die bei der Initialisierung der Entität erstellt wird. Weitere Informationen finden Sie in diesem großartigen Artikel .
- Verweisen Sie niemals auf verwandte Entitäten (ManyToOne). Wenn diese Entität (wie eine übergeordnete Entität) Teil des Geschäftsschlüssels sein muss, vergleichen Sie nur die IDs. Das Aufrufen von getId () auf einem Proxy löst das Laden der Entität nicht aus, solange Sie den Eigenschaftszugriffstyp verwenden .
Beispiel Entität
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Andere Vorschläge, die dieser Liste hinzugefügt werden sollen, sind mehr als willkommen ...
AKTUALISIEREN
Seit ich diesen Artikel gelesen habe, habe ich meine Art der Implementierung von eq / hC angepasst:
- Wenn ein unveränderlicher einfacher Geschäftsschlüssel verfügbar ist: Verwenden Sie diesen
- in allen anderen fällen: benutze eine uuid
final
(gemessen an Ihrer Auslassung von Setzern würde ich vermuten, dass Sie dies auch tun).notNull
das?Antworten:
Ich werde versuchen, einige wichtige Punkte zu beantworten: Dies beruht auf langjähriger Erfahrung im Ruhezustand / in der Persistenz, einschließlich mehrerer wichtiger Anwendungen.
Entitätsklasse: Serializable implementieren?
Schlüssel müssen Serializable implementieren. Dinge, die in die HttpSession gehen oder von RPC / Java EE über das Kabel gesendet werden, müssen Serializable implementieren. Andere Sachen: nicht so sehr. Verbringen Sie Ihre Zeit mit dem, was wichtig ist.
Konstruktoren: Erstellen Sie einen Konstruktor mit allen erforderlichen Feldern der Entität?
Konstruktoren für die Anwendungslogik sollten nur wenige kritische Felder "Fremdschlüssel" oder "Typ / Art" enthalten, die beim Erstellen der Entität immer bekannt sind. Der Rest sollte durch Aufrufen der Setter-Methoden festgelegt werden - dafür sind sie da.
Vermeiden Sie es, zu viele Felder in Konstruktoren einzufügen. Konstruktoren sollten bequem sein und dem Objekt grundlegende Vernunft verleihen. Name, Typ und / oder Eltern sind normalerweise nützlich.
OTOH Wenn die Anwendungsregeln (heute) erfordern, dass ein Kunde eine Adresse hat, überlassen Sie dies einem Setter. Das ist ein Beispiel für eine "schwache Regel". Vielleicht möchten Sie nächste Woche ein Kundenobjekt erstellen, bevor Sie zum Bildschirm Details eingeben wechseln? Stolpern Sie nicht, lassen Sie die Möglichkeit für unbekannte, unvollständige oder "teilweise eingegebene" Daten.
Konstruktoren: Paket privater Standardkonstruktor?
Ja, aber verwenden Sie "geschützt" anstatt "Paket privat". Unterklassen sind ein echtes Problem, wenn die erforderlichen Einbauten nicht sichtbar sind.
Felder / Eigenschaften
Verwenden Sie den Feldzugriff "Eigenschaft" für den Ruhezustand und von außerhalb der Instanz. Verwenden Sie innerhalb der Instanz die Felder direkt. Grund: Ermöglicht die Standardreflexion, die einfachste und grundlegendste Methode für den Ruhezustand.
Felder, die für die Anwendung unveränderlich sind, müssen im Ruhezustand noch geladen werden können. Sie können versuchen, diese Methoden "privat" zu machen und / oder sie mit Anmerkungen zu versehen, um zu verhindern, dass Anwendungscode unerwünschten Zugriff gewährt.
Hinweis: Wenn Sie eine equals () -Funktion schreiben, verwenden Sie Getter für Werte in der 'anderen' Instanz! Andernfalls werden Sie auf Proxy-Instanzen auf nicht initialisierte / leere Felder stoßen.
Geschützt ist besser für die Leistung im Ruhezustand?
Unwahrscheinlich.
Gleich / HashCode?
Dies ist relevant für die Arbeit mit Entitäten, bevor diese gespeichert wurden - was ein heikles Problem ist. Hashing / Vergleich mit unveränderlichen Werten? In den meisten Geschäftsanwendungen gibt es keine.
Ein Kunde kann die Adresse ändern, den Namen seines Unternehmens ändern usw. usw. - nicht üblich, aber es passiert. Korrekturen müssen auch möglich sein, wenn die Daten nicht korrekt eingegeben wurden.
Die wenigen Dinge, die normalerweise unveränderlich bleiben, sind Elternschaft und möglicherweise Typ / Art - normalerweise erstellt der Benutzer den Datensatz neu, anstatt diese zu ändern. Diese identifizieren die Entität jedoch nicht eindeutig!
Kurz und gut, die behaupteten "unveränderlichen" Daten sind nicht wirklich. Primärschlüssel- / ID-Felder werden genau zu dem Zweck generiert, eine solche garantierte Stabilität und Unveränderlichkeit zu gewährleisten.
Sie müssen Ihren Bedarf an Vergleichs-, Hashing- und Anforderungsverarbeitungsphasen planen und berücksichtigen, wenn Sie A) mit "geänderten / gebundenen Daten" auf der Benutzeroberfläche arbeiten, wenn Sie "selten geänderte Felder" vergleichen / hashen, oder B) mit "arbeiten". nicht gespeicherte Daten ", wenn Sie die ID vergleichen / hashen.
Equals / HashCode - Wenn kein eindeutiger Geschäftsschlüssel verfügbar ist, verwenden Sie eine nicht vorübergehende UUID, die bei der Initialisierung der Entität erstellt wird
Ja, dies ist bei Bedarf eine gute Strategie. Beachten Sie jedoch, dass UUIDs in Bezug auf die Leistung nicht kostenlos sind - und Clustering erschwert die Arbeit.
Equals / HashCode - Verweisen Sie niemals auf verwandte Entitäten
"Wenn eine verwandte Entität (wie eine übergeordnete Entität) Teil des Geschäftsschlüssels sein muss, fügen Sie ein nicht einfügbares, nicht aktualisierbares Feld hinzu, um die übergeordnete ID (mit demselben Namen wie die ManytoOne JoinColumn) zu speichern und diese ID bei der Gleichheitsprüfung zu verwenden ""
Klingt nach einem guten Rat.
Hoffe das hilft!
quelle
In der JPA 2.0-Spezifikation heißt es:
Die Spezifikation enthält keine Anforderungen an die Implementierung von equals- und hashCode-Methoden für Entitäten, soweit ich weiß, nur für Primärschlüsselklassen und Map-Schlüssel.
quelle
Meine 2 Cent zusätzlich zu den Antworten hier sind:
In Bezug auf den Feld- oder Eigenschaftszugriff (abgesehen von Leistungsüberlegungen) wird auf beide mit Hilfe von Gettern und Setzern legitim zugegriffen, sodass meine Modelllogik sie auf dieselbe Weise setzen / abrufen kann. Der Unterschied tritt auf, wenn der Persistenz-Laufzeitanbieter (Hibernate, EclipseLink oder sonst) einen Datensatz in Tabelle A beibehalten / festlegen muss, dessen Fremdschlüssel auf eine Spalte in Tabelle B verweist. Bei einem Eigenschaftszugriffstyp die Persistenz Das Laufzeitsystem verwendet meine codierte Setter-Methode, um der Zelle in Tabelle B einen neuen Wert zuzuweisen. Bei einem Feldzugriffstyp legt das Persistenz-Laufzeitsystem die Zelle in der Spalte Tabelle B direkt fest. Dieser Unterschied ist im Kontext einer unidirektionalen Beziehung nicht von Bedeutung. Es ist jedoch ein MUSS, meine eigene codierte Setter-Methode (Eigenschaftszugriffstyp) für eine bidirektionale Beziehung zu verwenden, vorausgesetzt, die Setter-Methode ist so konzipiert, dass sie die Konsistenz berücksichtigt. Konsistenz ist ein kritisches Thema für bidirektionale BeziehungenLink für ein einfaches Beispiel für einen gut gestalteten Setter.
In Bezug auf Equals / hashCode: Es ist unmöglich, die automatisch generierten Equals / hashCode-Methoden von Eclipse für Entitäten zu verwenden, die an einer bidirektionalen Beziehung teilnehmen. Andernfalls wird eine Zirkelreferenz erstellt, die zu einer Stackoverflow-Ausnahme führt. Sobald Sie eine bidirektionale Beziehung (z. B. OneToOne) versuchen und Equals () oder hashCode () oder sogar toString () automatisch generieren, werden Sie von dieser Stackoverflow-Ausnahme erfasst.
quelle
Entitätsschnittstelle
Grundlegende Implementierung für alle Entitäten, vereinfacht die Implementierung von Equals / Hashcode:
Raumentität impliziert:
Ich sehe keinen Grund, die Gleichheit von Entitäten basierend auf Geschäftsfeldern in jedem Fall von JPA-Entitäten zu vergleichen. Dies ist möglicherweise eher der Fall, wenn diese JPA-Entitäten als domänengesteuerte ValueObjects anstelle von domänengesteuerten Entitäten (für die diese Codebeispiele bestimmt sind) betrachtet werden.
quelle