Créer la parfaite entité JPA [fermé]
je travaille avec JPA (implementation 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, propriétés immuables, equals/hashCode,... .
J'ai donc décidé d'essayer de trouver la meilleure pratique générale pour chaque numéro et d'écrire ceci pour un usage personnel.
Cela ne me dérangerait cependant pas que quelqu'un commente ou me dise où j'ai tort.
Classe D'Entité
-
Sérialisable
raison: la spécification dit que vous devez, mais certains fournisseurs JPA ne font pas appliquer cela. Hibernation en tant que fournisseur JPA ne fait pas appliquer cela, mais il peut échouer quelque part profondément dans son estomac avec ClassCastException, si Serialisable n'a pas été mis en œuvre.
Constructeurs
-
créer un constructeur avec tous les champs de l'entité
raison: un constructeur doit toujours quitter l'instance créée dans un état sain.
-
outre ce constructeur: avoir un paquet private default constructor
raison: le constructeur par défaut est requis pour que Hibernate initialise l'entité; le privé est autorisé mais la visibilité privée (ou publique) du paquet est requise pour la génération de proxy d'exécution et l'extraction efficace des données sans instrumentation de bytecode.
Champs/Propriétés
-
utiliser l'accès au terrain en général et l'accès à la propriété au besoin
raison: c'est probablement la question la plus discutable car il n'y a pas d'arguments clairs et convaincants pour un ou l'autre (accès à la propriété vs accès au champ); cependant, l'accès au champ semble être le favori général en raison du code plus clair, d'une meilleure encapsulation et de l'absence de besoin de créer des setters pour les champs immuables
"
-
Omettre les setters pour immuable champs (non requis pour l'accès champ type)
- propriétés peut être privé
Raison: j'ai entendu une fois que protégé est mieux pour (Hibernate) performance mais tout ce que je peux trouver sur le web est: Hibernate peut accéder aux méthodes d'accesseurs publics, privés et protégés, ainsi que les domaines publics, privés et protégés directement. Le choix vous appartient et vous pouvez l'adapter à votre conception d'application.
est Égal à/hashCode
- N'utilisez jamais un id généré si cet id n'est défini qu'en cas de persistance de l'entité
- de préférence: utiliser des valeurs immuables pour former une clé commerciale unique et l'utiliser pour tester l'égalité
- si une clé D'affaires unique n'est pas disponible, utilisez un UUID qui est créé lorsque l'entité est initialisée; voir ce grand article pour plus d'informations.
- jamais se réfèrent à des entités liées (ManyToOne); si cette entité (comme une entité mère) doit faire partie de la Clé d'Entreprise alors comparez seulement les pièces d'identité. Appeler getId () sur un proxy ne déclenchera pas le chargement de l'entité, tant que vous utilisez 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é mon mode de mise en œuvre de l'eq/hC:
- si une clé simple d'affaires immuable est disponible: utiliser que
- dans tous les autres cas: utiliser un uuid
4 réponses
la spécification JPA 2.0 stipule que:
- la classe d'entité doit avoir un constructeur sans arg. Il peut avoir d'autres constructeurs. Le constructeur no-arg doit être public ou protégé.
- la classe d'entité doit être une classe supérieure. Un enum ou une interface ne doit pas être désigné comme une entité.
- la catégorie d'entité ne doit pas être finale. Pas de méthodes ou les variables d'instance persistantes de la classe entity peuvent être définitives.
- si une instance d'une entité doit être transmise par la valeur comme un objet détaché (par exemple, via une interface à distance), la classe d'entité doit implémenter l'interface sérialisable.
- les classes abstraites et concrètes peuvent être des entités. Les entités peuvent élargir les classes d'entité ainsi que les classes d'entité, et non les classes d'entité peut étendre les classes d'entité.
la spécification ne contient aucune exigence concernant la mise en œuvre des méthodes equals et hashCode pour les entités, seulement pour les classes de clés primaires et les clés de carte pour autant que je sache.
je vais essayer de répondre à plusieurs points clés: ceci est de longue expérience D'hibernation/ persistance, y compris plusieurs applications majeures.
Classe D'entité: implémenter Serialisable?
Clés doit implémenter Serializable. Les choses qui vont aller dans la HttpSession, OU être envoyées via le câble par RPC/Java EE, ont besoin d'implémenter Serialisable. Autres trucs: pas tellement. Passer votre temps sur ce qui est important.
Constructeurs: créer un constructeur avec tous les champs de l'entité?
Constructeur(s) pour l'application de la logique, ne devrait avoir que quelques critiques "clé étrangère" ou "type de/sorte de" champs qui vous permettra de toujours être connus lors de la création de l'entité. Le reste doit être réglé en appelant les méthodes setter -- c'est à ça qu'elles servent.
éviter de mettre trop de domaines dans les constructeurs. Constructeur devrait être pratique, et donner la santé mentale de base à l'objet. Le nom, le Type et/ou les Parents sont tous typiquement utiles.
otoh, que si l'application des règles (aujourd'hui) exiger d'un Client d'avoir une Adresse, un setter. C'est un exemple de "la faiblesse de l'état". Peut-être la semaine prochaine, vous voulez créer un objet client avant d'aller à L'écran entrer des détails? Ne vous trébuchez pas, laissez la possibilité pour les données inconnues, incomplètes ou" partiellement entrées".
Constructeurs: aussi, colis privé constructeur par défaut?
Oui, mais utilisez "protégé" plutôt que "privé". Sous-classement truc est une vraie douleur lorsque les internes ne sont pas visibles.
Champs/Propriétés
utiliser "propriété" accès au champ pour hibernation, et de l'extérieur de l'instance. Dans l'instance, utilisez les champs directement. Raison: permet une réflexion standard, la méthode la plus simple et la plus basique pour hiberner, pour travailler.
comme pour les champs "immuables" à l'application -- Hibernate doit encore pouvoir les charger. Vous pouvez essayer de rendre ces méthodes "privées", et / ou mettre une annotation sur elles, pour empêcher le code d'application rendre l'accès indésirable.
Note: lors de l'écriture d'une fonction equals (), utilisez getters pour les valeurs sur l'autre instance! Sinon, vous frapperez des champs non initialisés/ vides sur les instances de proxy.
protégé est-il meilleur pour les performances (D'hibernation)?
improbable.
Égale / HashCode?
ceci est pertinent pour travailler avec des entités, avant qu'elles n'aient été sauvées -- ce qui est une question épineuse. Le hachage/comparer sur des valeurs inaltérables? Dans la plupart des applications commerciales, il n'y en a pas.
un client peut changer d'adresse, changer le nom de leurs affaires, etc etc -- pas commun, mais il arrive. Des Corrections doivent également être possibles lorsque les données n'ont pas été saisies correctement.
les quelques choses qui sont normalement maintenues immuables, sont L'éducation et peut-être le Type/Genre -- normalement l'utilisateur recrée le dossier, plutôt que de les changer. Mais ceux-ci n'identifient pas uniquement l'entité!
donc, long et court, les données prétendues "immuables" ne sont pas vraiment. Les champs de la clé primaire/ ID sont générés pour le but précis, de fournir une telle stabilité garantie & immutabilité.
vous devez planifier et prendre en considération votre besoin de comparaison & de hachage & de traitement des demandes phases de travail lorsque A) travailler avec des" données modifiées/ reliées "de L'IU si vous comparez/hachez sur des" champs rarement modifiés", ou B) travailler avec des" données non sauvegardées", si vous comparez/hachez sur ID.
égale / HashCode -- si une clé D'affaires unique n'est pas disponible, utilisez une clé non transitoire UUID qui est créé lorsque l'entité est initialisée
Oui, c'est une bonne stratégie si nécessaire. Soyez conscient que les UUIDs ne sont pas libres, mais au niveau de la performance-et le regroupement complique les choses.
est Égal à/HashCode-de ne jamais se référer à des entités liées à
" si une entité apparentée (comme une entité mère) doit faire partie de la clé D'entreprise, Ajouter un champ non insertable, non modifiable pour stocker l'id de parent (avec le même nom que le ManytoOne JoinColumn) et utiliser cet id dans le contrôle de l'égalité "
sonne comme un bon conseil.
Espérons que cette aide!
mon supplément de 2 cents aux réponses ici sont:
-
en ce qui concerne L'accès au champ ou à la propriété (loin des considérations de performance) les deux sont légitimement accessibles au moyen de getters et de setters, ainsi, ma logique de modèle peut les définir/les obtenir de la même manière. La différence entre en jeu lorsque le fournisseur d'exécution de persistance (Hibernate, EclipseLink ou autre) doit persister/établir un certain record dans le tableau A qui a une clé étrangère se référant à une colonne du tableau B. Dans le cas d'un type D'accès à une propriété, le système d'exécution de persistence utilise ma méthode de setter codé pour attribuer 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 place directement la cellule dans la colonne du tableau B. Cette différence n'est pas importante dans le contexte d'une relation unidirectionnelle, mais il est nécessaire d'utiliser ma propre méthode de setter codé (Type D'accès à la propriété) pour une relation bidirectionnelle à condition que la méthode de setter soit bien conçu pour tenir compte de la cohérence. La cohérence est un problème critique pour les relations bi-directionnelles référez-vous à ce 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 D'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 résultant en un empilage Exception. Une fois que vous essayez une relation bidirectionnelle (par exemple OneToOne) et auto-generate Equals() ou hashCode() ou même toString (), vous serez pris dans cette exception stackoverflow.
Entité interface
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);
}
implémentation de base pour toutes les entités, simplifie les implémentations des égaux/Hashcodes:
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);
}
}
Salle Entité 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
}
je ne vois pas un point de comparaison l'égalité des entités basées sur les domaines d'activité, dans tous les cas, des Entités JPA. Cela pourrait être plus le cas si ces entités JPA sont considérées comme des objets de valeur axés sur le domaine plutôt que sur le domaine. Entités (auxquelles ces exemples de codes sont destinés).