java - with - standard jpa




Crea l'entità JPA perfetta (3)

I miei 2 centesimi in più alle risposte qui sono:

  1. Con riferimento all'accesso Field o Property (al di fuori delle considerazioni sulle prestazioni) entrambi sono legittimamente accessibili tramite getter e setter, quindi la logica del mio modello può impostarli / ottenerli nello stesso modo. La differenza arriva quando il provider di runtime di persistenza (Hibernate, EclipseLink o altro) ha bisogno di persistere / impostare alcuni record nella Tabella A che ha una chiave esterna che si riferisce ad alcune colonne nella Tabella B. Nel caso di un tipo di accesso Proprietà, la persistenza il sistema runtime usa il mio metodo setter settato per assegnare alla cella nella colonna Tabella B un nuovo valore. Nel caso di un tipo di accesso al campo, il sistema di runtime di persistenza imposta direttamente la cella nella colonna della tabella B. Questa differenza non è importante nel contesto di una relazione unidirezionale, tuttavia è OBBLIGATORIO utilizzare il mio metodo setter codificato (tipo di accesso alla proprietà) per una relazione bidirezionale, a condizione che il metodo setter sia ben progettato per tenere conto della coerenza . La coerenza è un aspetto critico per le relazioni bidirezionali che si riferiscono a questo link per un semplice esempio per un setter ben progettato.

  2. Con riferimento a Equals / hashCode: È impossibile utilizzare i metodi Equal / hashCode generati automaticamente da Eclipse per le entità che partecipano a una relazione bidirezionale, altrimenti avranno un riferimento circolare risultante in un'eccezione StackOverflow. Dopo aver provato una relazione bidirezionale (ad esempio OneToOne) e aver generato automaticamente Equals () o hashCode () o persino toString (), verrai sorpreso in questa eccezione di stackoverflow.

Ho lavorato con JPA (implementazione Hibernate) per un po 'di tempo e ogni volta che ho bisogno di creare entità mi trovo a dover lottare con problemi come AccessType, proprietà immutabili, equals / hashCode, ....
Così ho deciso di provare e scoprire le migliori pratiche generali per ogni problema e scriverlo per uso personale.
Non mi dispiacerebbe comunque che qualcuno potesse commentarlo o dirmi dove ho sbagliato.

Classe di entità

  • implementare serializzabile

    Motivo: le specifiche indicano che è necessario, ma alcuni provider JPA non lo applicano. Hibernate come provider JPA non impone questo, ma può fallire da qualche parte nel suo stomaco con ClassCastException, se Serializable non è stato implementato.

Costruttori

  • creare un costruttore con tutti i campi richiesti dell'entità

    Motivo: un costruttore dovrebbe sempre lasciare l'istanza creata in uno stato normale.

  • oltre a questo costruttore: avere un costruttore di default privato del pacchetto

    Motivo: è richiesto il costruttore predefinito per consentire a Hibernate di inizializzare l'entità; privato è consentito ma è necessaria visibilità del pacchetto privato (o pubblico) per la generazione del proxy di runtime e il recupero efficiente dei dati senza la strumentazione bytecode.

Campi / Proprietà

  • Utilizzare l'accesso al campo in generale e l'accesso alle proprietà quando necessario

    Motivo: questo è probabilmente il problema più discutibile poiché non ci sono argomenti chiari e convincenti per l'uno o l'altro (accesso alla proprietà vs accesso al campo); tuttavia, l'accesso al campo sembra essere il preferito generale a causa del codice più chiaro, dell'incapsulamento migliore e della necessità di creare setter per campi immutabili

  • Ometti setter per campi immutabili (non richiesto per il campo del tipo di accesso)

  • le proprietà possono essere private
    Motivo: una volta ho sentito che la protezione è migliore per le prestazioni (Hibernate), ma tutto quello che posso trovare sul Web è: Hibernate può accedere direttamente ai metodi di accesso pubblici, privati ​​e protetti, nonché ai campi pubblici, privati ​​e protetti. La scelta spetta a te e puoi abbinarla per adattarla al tuo design dell'applicazione.

Uguale / hashCode

  • Non utilizzare mai un ID generato se questo ID è impostato solo quando si mantiene l'entità
  • Di preferenza: usa valori immutabili per formare una chiave aziendale univoca e usala per testare l'uguaglianza
  • se non è disponibile una Business Key univoca utilizza un UUID non transitorio che viene creato quando l'entità viene inizializzata; Vedi questo fantastico articolo per maggiori informazioni.
  • non fare mai riferimento a entità correlate (ManyToOne); se questa entità (come un'entità padre) deve essere parte della Business Key, confrontare solo gli ID. Chiamare getId () su un proxy non innescherà il caricamento dell'entità, a condizione che si stia utilizzando il tipo di accesso alla proprietà .

Entità di esempio

@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

}

Altri suggerimenti da aggiungere a questa lista sono più che benvenuti ...

AGGIORNARE

Da quando ho letto questo articolo ho adattato il mio modo di implementare eq / hC:

  • se è disponibile una chiave aziendale semplice e immutabile: usala
  • in tutti gli altri casi: utilizzare un uuid

Interfaccia di entità

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);
}

Implementazione di base per tutte le entità, semplifica le implementazioni Equals / Hashcode:

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);
}
}

Entity Room Ent:

@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
}

Non vedo il punto di confrontare l'uguaglianza delle entità in base ai campi di attività in ogni caso delle Entità JPA. Questo potrebbe essere più di un caso se queste entità JPA sono considerate come ValueObjects guidato dal dominio, invece di entità basate sul dominio (che sono esempi di questi esempi di codice).


Proverò a rispondere a diversi punti chiave: si tratta della lunga esperienza di Hibernate / Persistenza che include diverse applicazioni importanti.

Entity Class: implement Serializable?

Chiavi deve implementare Serializable. Le cose che andranno nella HttpSession, o che saranno inviate da RPC / Java EE, devono implementare Serializable. Altre cose: non così tanto. Trascorri il tuo tempo su ciò che è importante.

Costruttori: creare un costruttore con tutti i campi richiesti dell'entità?

Costruttori (s) per la logica dell'applicazione, dovrebbero avere solo alcuni campi critici "chiave esterna" o "tipo / tipo" che saranno sempre noti durante la creazione dell'entità. Il resto dovrebbe essere impostato chiamando i metodi setter: ecco a cosa servono.

Evita di inserire troppi campi nei costruttori. I costruttori dovrebbero essere convenienti e dare la sanità mentale di base all'oggetto. Nome, tipo e / o genitori sono tutti in genere utili.

OTOH se le regole applicative (oggi) richiedono al cliente di avere un indirizzo, lasciare questo ad un setter. Questo è un esempio di "regola debole". Forse la prossima settimana, vuoi creare un oggetto Cliente prima di andare alla schermata Inserisci dettagli? Non inciampare, lasciare la possibilità di dati sconosciuti, incompleti o "parzialmente inseriti".

Costruttori: anche, pacchetto costruttore privato predefinito?

Sì, ma usare 'protetto' piuttosto che il pacchetto privato. Le sottoclassi sono un vero dolore quando gli interni necessari non sono visibili.

Campi / Proprietà

Utilizzare l'accesso al campo "proprietà" per Hibernate e dall'esterno. All'interno dell'istanza, utilizzare direttamente i campi. Motivo: consente di utilizzare la riflessione standard, il metodo più semplice e basilare per Hibernate.

Per quanto riguarda i campi "immutabili" per l'applicazione - Hibernate deve ancora essere in grado di caricarli. Potresti provare a rendere questi metodi "privati" e / o inserire un'annotazione su di essi, per evitare che il codice dell'applicazione renda l'accesso indesiderato.

Nota: quando si scrive una funzione equals (), utilizzare i getter per i valori sull'istanza "other"! Altrimenti, colpirai i campi non inizializzati / vuoti sulle istanze del proxy.

Protetto è meglio per (Hibernate) le prestazioni?

Improbabile.

Uguale / HashCode?

Questo è importante per lavorare con le entità, prima che siano state salvate - il che è un problema spinoso. Hashing / confronto su valori immutabili? Nella maggior parte delle applicazioni aziendali, non ce ne sono.

Un cliente può cambiare indirizzo, cambiare il nome della propria attività, ecc. Ecc. - non è comune, ma succede. Anche le correzioni devono essere possibili, quando i dati non sono stati inseriti correttamente.

Le poche cose che sono normalmente mantenute immutabili, sono Parenting e forse Type / Kind - normalmente l'utente ricrea il record, piuttosto che modificarlo. Ma questi non identificano univocamente l'entità!

Quindi, a lungo e breve, i dati "immutabili" dichiarati non sono realmente. I campi Chiave primaria / ID sono generati per lo scopo preciso, di fornire tale stabilità e immutabilità garantite.

È necessario pianificare e considerare la necessità di confrontare fasi di lavoro di hashing e di elaborazione delle richieste quando A) lavora con "dati modificati / vincolati" dall'interfaccia utente se si confronta / hash su "campi modificati di rado" o B) che funziona con " dati non salvati ", se si confronta / hash su ID.

Uguale a / HashCode: se non è disponibile una Business Key univoca, utilizzare un UUID non transitorio che viene creato quando l'entità viene inizializzata

Sì, questa è una buona strategia quando richiesto. Siate consapevoli del fatto che gli UUID non sono liberi, per quanto riguarda le prestazioni - e il clustering complica le cose.

Uguale a / HashCode - non fare mai riferimento alle entità correlate

"Se l'entità correlata (come un'entità padre) deve essere parte della Business Key, allora aggiungi un campo non inseribile, non aggiornabile per memorizzare l'id genitore (con lo stesso nome di ManytoOne JoinColumn) e usa questo id nel controllo di uguaglianza "

Sembra un buon consiglio.

Spero che questo ti aiuti!





equals