web-dev-qa-db-ger.com

Erstellen Sie die perfekte JPA-Entität

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, ....
Also beschloss ich, die allgemeinen Best Practices für jedes Problem herauszufinden und diese für den persönlichen Gebrauch aufzuschreiben.
Es würde mir jedoch nichts ausmachen, wenn sich jemand dazu äußert oder mir sagt, wo ich falsch liege.

Entitätsklasse

  • implementieren Serializable

    Grund: Die Spezifikation besagt, dass Sie müssen, aber einige JPA-Anbieter erzwingen dies nicht. Hibernate als JPA-Anbieter erzwingt dies nicht, kann aber mit ClassCastException irgendwo im Magen versagen, 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 gibt es einen privaten Standardkonstruktor für Pakete

    Grund: Der Standardkonstruktor ist erforderlich, damit Hibernate die Entität initialisiert. private ist zulässig, aber package private (oder public) Sichtbarkeit ist für die Laufzeit-Proxy-Generierung und das effiziente Abrufen von Daten ohne Bytecode-Instrumentierung erforderlich.

Felder/Eigenschaften

  • Verwenden Sie den allgemeinen Feldzugriff und bei Bedarf den Eigenschaftenzugriff

    Grund: Dies ist wahrscheinlich das umstrittenste Problem, da es keine klaren und überzeugenden Argumente für das eine oder andere gibt (Eigenschaftszugriff vs. Feldzugriff); Der Feldzugriff scheint jedoch aufgrund des klareren Codes, der besseren Kapselung und der Tatsache, dass keine Setter für unveränderliche Felder erstellt werden müssen, der allgemeine Favorit zu sein

  • Setter für unveränderliche Felder weglassen (nicht erforderlich für Zugriffstypfeld)

  • eigenschaften können privat sein
    Grund: Ich habe einmal gehört, dass protected für die Leistung im Ruhezustand besser ist, aber im Web kann ich nur Folgendes finden: Hibernate kann auf öffentliche, private und geschützte Zugriffsmethoden sowie auf public zugreifen. private und geschützte felder direkt. Die Wahl liegt bei Ihnen und Sie können sie an Ihr Anwendungsdesign anpassen.

Equals/hashCode

  • Verwenden Sie niemals eine generierte ID, wenn diese ID nur festgelegt wird, wenn die Entität bestehen bleibt
  • Vorzugsweise: 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 transiente [~ # ~] uuid [~ # ~] , die beim Initialisieren der Entität erstellt wird ; Weitere Informationen finden Sie in diesem großartigen Artikel .
  • verweisen nie 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. Wenn Sie getId () auf einem Proxy aufrufen, wird das Laden der Entität nicht ausgelöst, 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 sollten, sind mehr als willkommen ...

[~ # ~] Update [~ # ~]

Seit dem Lesen dieses Artikels 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: Verwenden Sie eine UUID
405
Stijn Geukens

Die JPA 2.0 Spezifikation besagt, dass:

  • Die Entitätsklasse muss einen Konstruktor ohne Argumente haben. Es kann auch andere Konstruktoren haben. Der Konstruktor no-arg muss öffentlich oder geschützt sein.
  • Die Entitätsklasse muss eine Klasse der obersten Ebene sein. Eine Aufzählung oder Schnittstelle darf nicht als Entität bezeichnet werden.
  • Die Entitätsklasse darf nicht final sein. Es dürfen keine Methoden oder persistenten Instanzvariablen der Entitätsklasse final sein.
  • Wenn eine Entitätsinstanz als getrenntes Objekt (z. B. über eine Remote-Schnittstelle) als Wert übergeben werden soll, muss die Entitätsklasse die serialisierbare Schnittstelle implementieren.
  • Sowohl abstrakte als auch konkrete Klassen können Entitäten sein. Entitäten können sowohl Nicht-Entitätsklassen als auch Entitätsklassen erweitern, und Nicht-Entitätsklassen können Entitätsklassen erweitern.

Die Spezifikation enthält meines Wissens keine Anforderungen zur Implementierung von equals- und hashCode-Methoden für Entitäten, sondern nur für Primärschlüsselklassen und Zuordnungsschlüssel.

141
Edwin Dalorzo

Ich werde versuchen, einige wichtige Punkte zu beantworten: Dies beruht auf langjähriger Erfahrung mit Ruhezustand/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 Netzwerk 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 "Fremdschlüssel" - oder "Typ/Art" -Felder 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 praktisch sein und dem Objekt grundlegende Vernunft verleihen. Name, Typ und/oder Eltern sind in der Regel alle nützlich.

OTOH Wenn die Anwendungsregeln (heute) erfordern, dass ein Kunde eine Adresse hat, überlassen Sie dies einem Einrichter. 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 gehen? Stolpern Sie nicht, lassen Sie die Möglichkeit für unbekannte, unvollständige oder "teilweise eingegebene" Daten.

Konstruktoren: auch private Standardkonstruktoren packen?

Ja, aber verwenden Sie "protected" anstelle von "package private". Subclassing Zeug ist ein echter Schmerz, wenn die notwendigen Interna 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 als "privat" zu kennzeichnen und/oder sie mit einer Anmerkung zu versehen, um zu verhindern, dass Anwendungscode unerwünschten Zugriff erhält.

Hinweis: Verwenden Sie beim Schreiben einer equals () -Funktion getters für Werte in der 'other'-Instanz! Andernfalls werden Sie auf Proxy-Instanzen auf nicht initialisierte/leere Felder klicken.

Geschützt ist besser für die Leistung im Ruhezustand?

Unwahrscheinlich.

Equals/HashCode?

Dies ist relevant für die Arbeit mit Entitäten, bevor sie gespeichert wurden - ein heikles Problem. Hashing/Vergleich von unveränderlichen Werten? In den meisten Geschäftsanwendungen gibt es keine.

Ein Kunde kann die Adresse ändern, den Namen seines Geschäfts ändern, usw. usw. - nicht üblich, aber es kommt vor. Korrekturen müssen auch möglich sein, wenn die Daten nicht korrekt eingegeben wurden.

Die wenigen Dinge, die normalerweise unveränderlich bleiben, sind Parenting und vielleicht Type/Kind - normalerweise erstellt der Benutzer den Datensatz neu, anstatt ihn 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 für den genauen Zweck generiert, eine solche garantierte Stabilität und Unveränderlichkeit bereitzustellen.

Sie müssen Ihren Bedarf an Vergleichs-, Hashing- und Anforderungsbearbeitungs-Arbeitsphasen planen und berücksichtigen, wenn Sie A) mit "geänderten/gebundenen Daten" von der Benutzeroberfläche aus arbeiten, wenn Sie "selten geänderte Felder" vergleichen/hashen, oder B) mit "arbeiten". ungespeicherte Daten ", wenn Sie/Hash auf ID vergleichen.

Equals/HashCode - Wenn kein eindeutiger Geschäftsschlüssel verfügbar ist, verwenden Sie eine nicht transiente UUID, die beim Initialisieren der Entität erstellt wird.

Ja, dies ist eine gute Strategie, wenn dies erforderlich ist. Beachten Sie, dass UUIDs in Bezug auf die Leistung nicht kostenlos sind - und Clustering kompliziert die Dinge.

Equals/HashCode - verweisen Sie niemals auf verwandte Entitäten

Msgstr "" "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!

67
Thomas W

Meine 2 Cent zusätzlich zu den Antworten hier sind:

  1. In Bezug auf den Feld- oder Eigenschaftszugriff (außerhalb der Leistungsüberlegungen) wird legitimerweise mit Hilfe von Gettern und Setzern auf beide zugegriffen, daher kann meine Modelllogik sie auf die gleiche Weise setzen/erhalten. Der Unterschied kommt zum Tragen, wenn der Persistenz-Laufzeitanbieter (Ruhezustand, EclipseLink oder anderes) einen Datensatz in Tabelle A beibehalten/festlegen muss, der einen Fremdschlüssel aufweist, der auf eine Spalte in Tabelle B verweist. Im Fall eines Eigenschaftszugriffstyps die Persistenz Das Laufzeitsystem verwendet die Methode my coded setter, um der Zelle in der Spalte "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 Zusammenhang mit einer unidirektionalen Beziehung nicht von Bedeutung. Es ist jedoch ein MUSS, für eine bidirektionale Beziehung meine eigene codierte Setter-Methode (Eigenschaftszugriffstyp) zu verwenden, vorausgesetzt, die Setter-Methode wurde so konzipiert, dass die Konsistenz berücksichtigt wird . Konsistenz ist ein kritisches Thema für bidirektionale Beziehungen. Lesen Sie hierzu link für ein einfaches Beispiel für einen gut gestalteten Setter.

  2. Mit Bezug auf Equals/hashCode: Es ist unmöglich, die von Eclipse automatisch generierten Equals/hashCode-Methoden für Entitäten zu verwenden, die an einer bidirektionalen Beziehung teilnehmen, da sie sonst einen Zirkelverweis haben, der zu einer Stackoverflow-Ausnahme führt. Sobald Sie eine bidirektionale Beziehung versuchen (z. B. OneToOne) und Equals () oder hashCode () oder sogar toString () automatisch generieren, werden Sie von dieser Stackoverflow-Ausnahme erfasst.

13
Sym-Sym

Entitätsschnittstelle

public interface Entity<I> extends Serializable {

/**
 * @return entity identity
 */
I getId();

/**
 * @return HashCode of entity identity
 */
int identityHashCode();

/**
 * @param other
 *            Other entity
 * @return true if identities of entities are equal
 */
boolean identityEquals(Entity<?> other);
}

Basisimplementierung für alle Entities, vereinfacht Equals/Hashcode-Implementierungen:

public abstract class AbstractEntity<I> implements Entity<I> {

@Override
public final boolean identityEquals(Entity<?> other) {
    if (getId() == null) {
        return false;
    }
    return getId().equals(other.getId());
}

@Override
public final int identityHashCode() {
    return new HashCodeBuilder().append(this.getId()).toHashCode();
}

@Override
public final int hashCode() {
    return identityHashCode();
}

@Override
public final boolean equals(final Object o) {
    if (this == o) {
        return true;
    }
    if ((o == null) || (getClass() != o.getClass())) {
        return false;
    }

    return identityEquals((Entity<?>) o);
}

@Override
public String toString() {
    return getClass().getSimpleName() + ": " + identity();
    // OR 
    // return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}

Raumeinheit impl:

@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@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;
}

public Integer getId(){
    return id;
}

public Building getBuilding() {
    return building;
}

public String getNumber() {
    return number;
}


public void setCapacity(Integer capacity) {
    this.capacity = capacity;
}

//no setters for number, building nor id
}

Ich sehe keinen Sinn darin, 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 anstatt als domänengesteuerte Entitäten (für die diese Codebeispiele gelten) betrachtet werden.

9
ahaaman