Lavoro con JPA (implementazione Hibernate) da un po 'di tempo e ogni volta che devo creare entità mi trovo alle prese con problemi come AccessType, proprietà immutabili, equals / hashCode, ....
Così ho deciso di provare a scoprire le migliori pratiche generali per ogni problema e scriverlo per uso personale.
Non mi dispiacerebbe comunque che qualcuno commentasse o mi dicesse dove sbaglio.
Classe di entità
implementare serializzabile
Motivo: le specifiche indicano che è necessario, ma alcuni provider JPA non lo applicano. Sospendere come provider JPA non impone questo, ma può fallire da qualche parte nel profondo con ClassCastException, se Serializable non è stato implementato.
Costruttori
creare un costruttore con tutti i campi obbligatori dell'entità
Motivo: un costruttore deve sempre lasciare l'istanza creata in uno stato sano.
oltre a questo costruttore: avere un costruttore predefinito del pacchetto
Motivo: per consentire a Hibernate di inizializzare l'entità è necessario il costruttore predefinito; privato è consentito ma la visibilità del pacchetto privato (o pubblico) è richiesta per la generazione del proxy di runtime e il recupero efficiente dei dati senza strumentazione bytecode.
Campi / Proprietà
Utilizzare l'accesso al campo in generale e l'accesso alla proprietà quando necessario
Motivo: questo è probabilmente il problema più discutibile in quanto non esistono argomenti chiari e convincenti per l'uno o l'altro (accesso alla proprietà vs accesso al campo); tuttavia, l'accesso ai campi sembra essere il preferito in 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 (ibernazione) ma tutto ciò che posso trovare sul web è: Hibernate può accedere a metodi di accesso pubblici, privati e protetti, nonché direttamente ai campi pubblici, privati e protetti . La scelta spetta a te e puoi abbinarla per adattarla al design della tua applicazione.
Equals / hashCode
- Non utilizzare mai un ID generato se questo ID è impostato solo quando si persiste l'entità
- Per preferenza: utilizzare valori immutabili per formare una chiave aziendale univoca e utilizzarla per testare l'uguaglianza
- se non è disponibile una chiave business univoca, utilizzare un UUID non transitorio che viene creato al momento dell'inizializzazione dell'entità; Vedi questo fantastico articolo per ulteriori informazioni.
- non fare mai riferimento a entità correlate (ManyToOne); se questa entità (come un'entità principale) deve far parte della chiave aziendale, confronta solo l'ID. La chiamata di getId () su un proxy non attiverà il caricamento dell'entità, purché si utilizzi 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 questo elenco sono più che benvenuti ...
AGGIORNARE
Da quando ho letto questo articolo ho adattato il mio modo di implementare eq / hC:
- se è disponibile una chiave business semplice immutabile: usala
- in tutti gli altri casi: utilizzare un uuid
final
(a giudicare dalla tua omissione di setter, immagino che lo faccia anche tu).
notNull
viene