Je travaille avec JPA (implémentation Hibernate) depuis un certain temps maintenant et chaque fois que j'ai besoin de créer des entités, je me retrouve avec des problèmes comme AccessType, les propriétés immuables, equals / hashCode, ....
J'ai donc décidé d'essayer de trouver la meilleure pratique générale pour chaque problème et de l'écrire pour un usage personnel.
Cela ne me dérangerait cependant pas que quiconque commente ou me dise où je me trompe.
Classe d'entité
implémenter Serializable
Raison: la spécification dit que vous devez le faire, mais certains fournisseurs JPA ne l'appliquent pas. Hibernate en tant que fournisseur JPA n'applique pas cela, mais il peut échouer quelque part au fond de son estomac avec ClassCastException, si Serializable n'a pas été implémenté.
Constructeurs
créer un constructeur avec tous les champs obligatoires de l'entité
Raison: un constructeur doit toujours laisser l'instance créée dans un état sain.
en plus de ce constructeur: avoir un package constructeur par défaut privé
Motif: le constructeur par défaut doit demander à Hibernate d'initialiser l'entité; private est autorisé mais la visibilité privée (ou publique) du package est requise pour la génération de proxy d'exécution et la récupération efficace des données sans instrumentation de bytecode.
Champs / Propriétés
Utilisez l'accès aux champs en général et l'accès à la propriété en cas de besoin
Raison: c'est probablement la question la plus discutable car il n'y a pas d'arguments clairs et convaincants pour l'un ou l'autre (accès à la propriété vs accès au champ); cependant, l'accès aux champs semble être le favori général en raison d'un code plus clair, d'une meilleure encapsulation et de la nécessité de créer des paramètres pour les champs immuables
Omettre les paramètres pour les champs immuables (non requis pour le champ de type d'accès)
- les propriétés peuvent être privées
Raison: j'ai entendu dire que protégé est meilleur pour les performances (mise en veille prolongée), mais tout ce que je peux trouver sur le Web est: Hibernate peut accéder aux méthodes d'accesseur publiques, privées et protégées, ainsi qu'aux champs publics, privés et protégés directement . Le choix vous appartient et vous pouvez l'adapter à la conception de votre application.
Equals / hashCode
- N'utilisez jamais un identifiant généré si cet identifiant est uniquement défini lors de la persistance de l'entité
- De préférence: utilisez des valeurs immuables pour former une clé métier unique et utilisez-la pour tester l'égalité
- si une clé d'entreprise unique n'est pas disponible, utilisez un UUID non transitoire qui est créé lors de l'initialisation de l'entité; Consultez cet excellent article pour plus d'informations.
- ne faites jamais référence à des entités liées (ManyToOne); si cette entité (comme une entité parente) doit faire partie de la clé d'entreprise, comparez uniquement les ID. L'appel de getId () sur un proxy ne déclenchera pas le chargement de l'entité, tant que vous utilisez le type d'accès à la propriété .
Exemple d'entité
@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
}
D'autres suggestions à ajouter à cette liste sont plus que bienvenues ...
MISE À JOUR
Depuis la lecture de cet article, j'ai adapté ma façon de mettre en œuvre eq / hC:
- si une clé métier simple immuable est disponible: utilisez-la
- dans tous les autres cas: utilisez un uuid
final
(à en juger par votre omission des setters, je suppose que vous aussi).notNull
vient-il?Réponses:
Je vais essayer de répondre à plusieurs points clés: cela vient d'une longue expérience de mise en veille prolongée / persistance, y compris plusieurs applications majeures.
Classe d'entité: implémenter Serializable?
Les clés doivent implémenter Serializable. Les trucs qui vont aller dans la session HttpSession, ou être envoyés sur le câble par RPC / Java EE, doivent implémenter Serializable. Autres trucs: pas tellement. Passez votre temps sur ce qui est important.
Constructeurs: créer un constructeur avec tous les champs obligatoires de l'entité?
Le ou les constructeurs de la logique d'application ne devraient avoir que quelques champs "clé étrangère" ou "type / type" critiques qui seront toujours connus lors de la création de l'entité. Le reste doit être défini en appelant les méthodes de définition - c'est à cela qu'elles servent.
Évitez de mettre trop de champs dans les constructeurs. Les constructeurs doivent être pratiques et donner un bon sens de base à l'objet. Le nom, le type et / ou les parents sont généralement utiles.
OTOH si les règles d'application (aujourd'hui) exigent qu'un client ait une adresse, laissez-la à un passeur. C'est un exemple de "règle faible". La semaine prochaine, vous souhaitez peut-être créer un objet Client avant d'accéder à l'écran Entrer les détails? Ne vous trébuchez pas, laissez la possibilité de données inconnues, incomplètes ou "partiellement saisies".
Constructeurs: également, package constructeur privé par défaut?
Oui, mais utilisez «protégé» plutôt que package privé. Le sous-classement est un vrai problème lorsque les composants internes nécessaires ne sont pas visibles.
Champs / Propriétés
Utilisez l'accès au champ «propriété» pour Hibernate et depuis l'extérieur de l'instance. Dans l'instance, utilisez directement les champs. Raison: permet à la réflexion standard, la méthode la plus simple et la plus basique d'Hibernate, de fonctionner.
Quant aux champs «immuables» à l'application - Hibernate doit toujours être capable de les charger. Vous pouvez essayer de rendre ces méthodes «privées» et / ou de mettre une annotation dessus, pour empêcher le code d'application de créer un accès indésirable.
Remarque: lors de l'écriture d'une fonction equals (), utilisez des getters pour les valeurs de l'instance 'other'! Sinon, vous frapperez des champs non initialisés / vides sur les instances de proxy.
Protégé est meilleur pour les performances (Hibernate)?
Peu probable.
Equals / HashCode?
Cela est pertinent pour travailler avec des entités, avant qu'elles ne soient enregistrées - ce qui est un problème épineux. Hachage / comparaison sur des valeurs immuables? Dans la plupart des applications d'entreprise, il n'y en a pas.
Un client peut changer d'adresse, changer le nom de son entreprise, etc., etc. - ce n'est pas courant, mais cela arrive. Des corrections doivent également être possibles lorsque les données n'ont pas été saisies correctement.
Les quelques éléments qui restent normalement immuables sont la parentalité et peut-être le type / type - normalement, l'utilisateur recrée l'enregistrement plutôt que de les modifier. Mais ceux-ci n'identifient pas uniquement l'entité!
Donc, longues et courtes, les données "immuables" revendiquées ne le sont pas vraiment. Les champs de clé primaire / ID sont générés dans le but précis de fournir une telle stabilité et immuabilité garanties.
Vous devez planifier et prendre en compte votre besoin de phases de travail de comparaison et de hachage et de traitement des demandes lorsque A) travaillez avec des "données modifiées / liées" à partir de l'interface utilisateur si vous comparez / hachez sur des "champs rarement modifiés", ou B) travaillez avec " données non enregistrées ", si vous comparez / hachez sur l'ID.
Equals / HashCode - si une clé métier unique n'est pas disponible, utilisez un UUID non transitoire qui est créé lors de l'initialisation de l'entité
Oui, c'est une bonne stratégie si nécessaire. Sachez que les UUID ne sont pas gratuits, en termes de performances, et que le clustering complique les choses.
Equals / HashCode - ne faites jamais référence à des entités liées
"Si une entité associée (comme une entité parent) doit faire partie de la clé métier, ajoutez un champ non insérable et non modifiable pour stocker l'ID parent (avec le même nom que ManytoOne JoinColumn) et utilisez cet identifiant dans la vérification d'égalité "
Cela ressemble à de bons conseils.
J'espère que cela t'aides!
la source
La spécification JPA 2.0 stipule que:
La spécification ne contient aucune exigence concernant l'implémentation des méthodes equals et hashCode pour les entités, uniquement pour les classes de clés primaires et les clés de carte pour autant que je sache.
la source
Mon ajout de 2 cents aux réponses ici est:
En ce qui concerne l'accès aux champs ou aux propriétés (loin des considérations de performances), les deux sont légitimement accessibles au moyen de getters et de setters, donc ma logique de modèle peut les définir / les obtenir de la même manière. La différence vient à jouer lorsque le fournisseur d'exécution de persistance (Hibernate, EclipseLink ou autre) doit persister / définir un enregistrement dans le tableau A qui a une clé étrangère faisant référence à une colonne du tableau B. Dans le cas d'un type d'accès à la propriété, la persistance Le système d'exécution utilise ma méthode de définition codée pour affecter une nouvelle valeur à la cellule de la colonne du tableau B. Dans le cas d'un type d'accès au champ, le système d'exécution de persistance définit directement la cellule dans la colonne du tableau B. Cette différence n'a pas d'importance dans le contexte d'une relation unidirectionnelle, Pourtant, il est impératif d'utiliser ma propre méthode de définition codée (type d'accès à la propriété) pour une relation bidirectionnelle à condition que la méthode de définition soit bien conçue pour tenir compte de la cohérence. La cohérence est un problème critique pour les relations bidirectionnelles.lien pour un exemple simple pour un setter bien conçu.
En référence à Equals / hashCode: Il est impossible d'utiliser les méthodes Equals / hashCode générées automatiquement par Eclipse pour les entités participant à une relation bidirectionnelle, sinon elles auront une référence circulaire entraînant une exception de débordement de pile. Une fois que vous essayez une relation bidirectionnelle (disons OneToOne) et que vous générez automatiquement Equals () ou hashCode () ou même toString (), vous serez pris dans cette exception de stackoverflow.
la source
Interface d'entité
Implémentation de base pour toutes les entités, simplifie les implémentations Equals / Hashcode:
Entité de salle impl:
Je ne vois pas l'intérêt de comparer l'égalité des entités en fonction des domaines d'activité dans chaque cas d'entités JPA. Cela pourrait être plus un cas si ces entités JPA sont considérées comme des objets de valeur pilotés par domaine, au lieu d'entités pilotées par domaine (pour lesquelles ces exemples de code sont destinés).
la source