435 votes

Créez le mix parfait

J'ai travaillé avec JPA (mise en veille prolongée) pour un certain temps connaître et à chaque fois que j'ai besoin de créer des entités de me retrouver aux prises avec des problèmes comme AccessType, immuable propriétés, est égal à/hashCode, ... .
J'ai donc décidé de l'essayer et de trouver la meilleure pratique générale pour chaque question et d'écrire ces vers le bas pour un usage personnel.
Je ne serait pas l'esprit cependant, pour toute personne à faire un commentaire ou de me dire où je me trompe.

Classe D'Entité

  • Sérialisable

    Raison: La spécification indique que vous avez, mais certains JPA fournisseurs de ne pas respecter cela. Hibernate comme fournisseur JPA ne pas respecter cela, mais il peut échouer quelque part au fond de son estomac avec ClassCastException, si Serializable n'a pas été mis en œuvre.

Les constructeurs

  • créer un constructeur avec tous les champs de l'entité

    La raison: Un constructeur doit toujours laisser l'instance créée dans un état sain.

  • en plus de ce constructeur: avoir un paquet privée constructeur par défaut

    Raison: la valeur par Défaut du constructeur est tenu de disposer d'Hibernate initialiser l'entité; privé est autorisée, mais colis privé (ou public) la visibilité est nécessaire pour l'exécution génération de proxy et efficace de récupération de données sans instrumentation du bytecode.

Champs/Propriétés

  • Utiliser le champ de l'accès en général et l'accès à la propriété lorsque nécessaire

    La raison: c'est probablement le plus discutable problème puisqu'il n'existe pas de relation claire et convaincante les arguments pour l'une ou l'autre (l'accès à la propriété vs d'accès sur le terrain); cependant, le champ de l'accès semble être générale favori en raison de code plus clair, mieux encapsulation et pas besoin de créer des ouvreurs de immuable champs

  • Omettre les setters pour immuable champs (non requis pour l'accès type de champ)

  • les propriétés peuvent être privés
    La raison: une fois, j'ai entendu dire que les protégés de mieux pour (Hibernate) de performance, mais tout ce que je peux trouver sur le web est: Hibernate peut accès public, privé et protégé méthodes d'accès, aussi bien que public, privé et protégé des champs directement. Le choix est à vous et vous pouvez le faire correspondre à votre conception de l'application.

Est égal à/hashCode

  • Ne jamais utiliser un id généré si cet id est défini uniquement lorsque la persistance de l'entité
  • Par préférence: utilisation des valeurs inaltérables pour former une unique Clé d'Entreprise et de l'utiliser pour tester l'égalité
  • si une Entreprise unique Clé n'est pas disponible, utilisez un non-transitoire UUID qui est créé lorsque l'entité est initialisé
  • ne jamais se référer à des entités liées (ManyToOne); si cette entité (comme une entité mère) doit faire partie de la Clé d'Entreprise puis de comparer les ID. L'appel de getId() sur un proxy ne sera pas déclencher le chargement de l'entité, aussi longtemps que vous êtes à l'aide de la propriété type d'accès.

Exemple D'Entité

@Entity
@Table(name = "ROOM")
public class Room implements Serializable {

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

    @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
        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 bienvenus...

148voto

Edwin Dalorzo Points 19899

La JPA 2.0 Spécification précise que:

  • La classe d'entité doit avoir un no-arg constructeur. Il peut avoir d'autres constructeurs. Le non-arg constructeur doit être public ou protégé.
  • La classe d'entité doit être de haut niveau de classe. Un enum ou de l'interface ne doit pas être désigné comme une entité.
  • La classe d'entité ne doit pas être définitive. Pas de méthodes ou la persistance des variables d'instance de la classe d'entité peut être final.
  • Si une entité instance est passé par valeur en tant que détaché de l'objet (par exemple, par le biais d'une interface à distance), l'entité de la classe doit implémenter l'interface Serializable.
  • À la fois abstrait et concret classes 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 contient pas d'exigences concernant la mise en œuvre de equals et hashCode méthodes pour les entités, seulement pour la clé primaire classes et carte des clés pour autant que je sais.

74voto

Thomas W Points 7179

Je vais essayer de répondre sur plusieurs points: c'est à partir de longues Hibernate/ persistance de l'expérience, y compris plusieurs des principales applications.

Classe d'entité: Sérialisable?

Touches doit implémenter Serializable. Des trucs qui va aller dans le HttpSession, ou être envoyés par RPC/Java EE, doit implémenter Serializable. 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 mis en appelant les méthodes de définition, c'est ce qu'ils sont pour.

Éviter de mettre trop de champs dans les constructeurs. Les constructeurs devraient être pratique, et donner de base en santé mentale de l'objet. Le nom, le Type et/ou les Parents sont tous généralement utile.

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 Client de l'objet avant de passer à l'Entrée des Détails de l'écran? Ne pas le voyage vous-même, laissez possibilité pour l'inconnu, incomplète ou "partiellement saisi des données".

Constructeurs: aussi, colis privé constructeur par défaut?

Oui, mais l'utilisation de "protégés" plutôt que de colis privé. Sous-classement truc est une vraie douleur lorsque les internes ne sont pas visibles.

Champs/Propriétés

L'utilisation de 'propriété' accès sur le terrain pour la mise en veille prolongée, et à partir de l'extérieur de l'instance. Au sein de l'instance, utilisez les champs directement. Le but: permettre aux standard de la réflexion, de la plus simple et la plus basique, la méthode de mise en veille prolongée au travail.

Comme pour les champs "immuable" à la demande -- Hibernate doit encore être en mesure de charger ces. Vous pouvez essayer de faire de ces méthodes de "privé", et/ou de mettre une annotation sur eux pour empêcher l'application code de rendre l'accès indésirable.

Remarque: lors de l'écriture d'une equals() de la fonction, utiliser les accesseurs pour les valeurs de "l'autre" instance! Sinon, vous serez frappé de non initialisée/ champs vides sur proxy instances.

Protégé est mieux pour (Hibernate) la performance?

Peu probable.

Est Égal À/HashCode?

C'est intéressant de travailler avec des entités, avant qu'ils ont été enregistrés, ce qui est un problème épineux. Le hachage/comparer sur des valeurs inaltérables? Dans la plupart des applications d'entreprise, il n'y en a pas.

Un client peut changer d'adresse, de changer le nom de leur entreprise, etc etc ... pas courant, mais ça arrive. Les Corrections doivent également être possible de faire, lorsque les données n'a pas été entré correctement.

Le peu de choses qui sont normalement conservés immuable, sont la Parentalité et peut-être de Type/Genre -- normalement, l'utilisateur recrée le dossier, plutôt que de changer ces. Mais elles ne permettent pas d'identifier de manière unique l'entité!

Donc, à court et long, le prétendu "immuable" de données n'est pas vraiment le cas. Clé primaire/ ID champs sont générés dans le but précis de fournir de telles garanties de stabilité et de l'immuabilité.

Vous avez besoin pour planifier et tenir compte de votre besoin à des fins de comparaison et de hachage et de traitement des demandes phases de travail lorsque: A) travailler avec des "modifications de données lié" à partir de l'INTERFACE utilisateur si vous comparer/hachage sur "rarement changé champs", ou B) à travailler avec des données non enregistrées", si l'on compare/hash ID.

Est égal à/HashCode -- si une Entreprise unique Clé n'est pas disponible, utiliser un non-transitoire UUID qui est créé lorsque l'entité est initialisé

Oui, c'est une bonne stratégie si nécessaire. Être conscient que l'Uuid ne sont pas libres, en terme de performance si -- et clustering complique les choses.

Est égal à/HashCode-de ne jamais se référer à des entités liées

"Si l'entité (comme une entité mère) doit faire partie de la Clé d'Entreprise puis ajouter un non insérable, non actualisable champ pour stocker l'id parent (avec le même nom que le ManytoOne JoinColumn) et d'utiliser ce code dans le contrôle d'égalité"

Sonne comme un bon conseil.

Espérons que cette aide!

15voto

Sam Points 1078

Après avoir exprimé mon admiration pour le Stijns semi-liste complète, 2 corrections sont:

  1. Avec la référence de Champ ou de l'accès à la Propriété (loin des considérations de performance), les deux sont légitimement accessibles que par le biais des getters et setters, donc, mon modèle logique peut set/get de la même manière. La différence vient de jouer lorsque la persistance d'exécution du fournisseur (Hibernate, EclipseLink ou autre) doit persister/définir certaines enregistrement dans Une Table qui a une clé étrangère qui fait référence à certains de colonne dans le Tableau B. Dans le cas d'une Propriété type d'accès, la persistance d'exécution système utilise mon codé le setter de la méthode d'attribution de la cellule dans le Tableau de la colonne B une nouvelle valeur. Dans le cas d'un Champ de type d'accès, la persistance d'exécution du système de jeux de la cellule dans le Tableau de la colonne B directement. Cette différence n'est pas de l'importance dans le contexte d'une uni-directionnelle de relations, mais il est indispensable d'utiliser mon propre codé méthode de définition (Propriété type d'accès) pour entretenir une relation bidirectionnelle fourni la méthode setter est bien conçu pour tenir compte de la cohérence. La cohérence est une question cruciale pour les bi-directionnelle des relations consultez ce lien pour un exemple simple pour bien concevoir un setter.

  2. Avec référence à Égale/hashCode: Il est impossible d'utiliser l'Éclipse auto-générés est Égal à/hashCode méthodes pour les entités participant à un bi-directionnelle de la relation, sinon ils vont avoir une référence circulaire résultant en une stackoverflow Exception. Une fois que vous essayez une relation bidirectionnelle (dire OneToOne) et auto-générer Equals() ou hashCode() ou même toString() vous obtiendrez pris dans cette stackoverflow exception.

9voto

ahaaman Points 176

Entité de l'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);
}

Mise en œuvre de base pour toutes les Entités, simplifie Égale/Hashcode implémentations:

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

Chambre 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. Qui pourrait être plus d'un cas si ces entités JPA sont considérés comme Domain-Driven ValueObjects, au lieu de Domain-Driven Entités (qui ces exemples de code sont pour).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X