web-dev-qa-db-fra.com

Hibernate Annotations - Quel est le meilleur accès aux champs ou aux propriétés?

Cette question est quelque peu liée à Question de placement d'annotation Hibernate .

Mais je veux savoir ce qui est mieux? Accès via les propriétés ou via les champs? Quels sont les avantages et les inconvénients de chacun?

119
Martin OConnor

Je préfère les accesseurs, car je peux ajouter une logique métier à mes accesseurs chaque fois que j'ai besoin de ..En voici un exemple:

@Entity
public class Person {

  @Column("nickName")
  public String getNickName(){
     if(this.name != null) return generateFunnyNick(this.name);
     else return "John Doe";
  }
}

En outre, si vous ajoutez d'autres bibliothèques au mélange (comme certaines bibliothèques convertissant JSON, BeanMapper ou Dozer ou d'autres bibliothèques de mappage/clonage de beans basées sur les propriétés de lecture/définition), vous aurez la garantie que la bibliothèque est synchronisée avec la persistance gestionnaire (les deux utilisent le getter/setter).

31
Miguel Ping

Il existe des arguments pour les deux, mais la plupart d'entre eux découlent de certaines exigences de l'utilisateur "Que se passe-t-il si vous devez ajouter une logique pour" ou "xxxx rompt l'encapsulation". Cependant, personne n’a réellement commenté la théorie et donné un argument dûment motivé.

Que fait réellement Hibernate/JPA quand il persiste sur un objet - eh bien, il persiste dans l'état de l'objet. Cela signifie qu'il doit être stocké de manière à pouvoir être facilement reproduit.

Qu'est-ce que l'encapsulation? Encapsulations signifie encapsuler les données (ou l'état) avec une interface que l'application/le client peut utiliser pour accéder aux données en toute sécurité - en les maintenant cohérentes et valides.

Pensez à cela comme MS Word. MS Word conserve un modèle du document en mémoire - les documents STATE. Il présente une interface que l’utilisateur peut utiliser pour modifier le document - un ensemble de boutons, d’outils, de commandes au clavier, etc. Cependant, lorsque vous choisissez de conserver (Enregistrer) ce document, il enregistre l’état interne, et non l’ensemble des touches clics de souris utilisés pour le générer.

L'enregistrement de l'état interne de l'objet ne rompt PAS l'encapsulation. Sinon, vous ne comprenez pas vraiment ce que signifie l'encapsulation et pourquoi il existe. C'est vraiment comme la sérialisation d'objet.

Pour cette raison, DANS LA PLUPART DES CAS, il convient de conserver les champs et non les accessoires. Cela signifie qu'un objet peut être recréé avec précision à partir de la base de données exactement comme il a été stocké. Aucune validation n'est nécessaire car elle a été effectuée sur l'original lors de sa création et avant son stockage dans la base de données (sauf indication contraire, vous stockez des données non valides dans la base de données !!!!). De même, il ne devrait pas être nécessaire de calculer des valeurs, car elles avaient déjà été calculées avant le stockage de l'objet. L'objet doit avoir exactement la même apparence qu'avant sa sauvegarde. En fait, en ajoutant des éléments supplémentaires dans les getters/setters, vous êtes réellement en augmentant le risque de recréer quelque chose qui n’est pas une copie exacte de l’original.

Bien sûr, cette fonctionnalité a été ajoutée pour une raison. Il peut y avoir des cas d'utilisation valides pour persister les accesseurs, mais ils seront généralement rares. Par exemple, vous pouvez éviter de conserver une valeur calculée, mais vous pouvez également vous demander pourquoi vous ne la calculez pas à la demande dans le getter de la valeur ou initialisez-la lentement dans le getter. Personnellement, je ne peux pas penser à un bon cas d'utilisation, et aucune des réponses ici ne donne vraiment une réponse "Software Engineering".

235
Martin

Je préfère les accès sur le terrain, car je ne suis donc pas obligé de fournir un getter/setter pour chaque propriété.

Un rapide sondage via Google suggère que l’accès aux champs est majoritaire (par exemple, http://Java.dzone.com/tips/12-feb-jpa-20-why-accesstype ).

Je crois que l'accès aux champs est l'idiome recommandé par Spring, mais je ne trouve pas de référence pour le sauvegarder.

Il y a une question liée à SO qui a essayé de mesurer les performances et est parvenue à la conclusion qu'il n'y avait "pas de différence".

76
duffymo

Voici une situation où vous devez utiliser des accesseurs de propriété. Imaginez que vous ayez une classe abstraite GÉNÉRIQUE avec beaucoup de qualités d'implémentation à hériter dans 8 sous-classes concrètes:

public abstract class Foo<T extends Bar> {

    T oneThing;
    T anotherThing;

    // getters and setters ommited for brevity

    // Lots and lots of implementation regarding oneThing and anotherThing here
 }

Maintenant, comment exactement comment annoter cette classe? La réponse est: VOUS NE POUVEZ PAS l'annoter du tout avec un accès à un champ ou à une propriété, car vous ne pouvez pas spécifier l'entité cible à ce stade. Vous devez annoter les implémentations concrètes. Mais comme les propriétés persistantes sont déclarées dans cette superclasse, vous DEVEZ utiliser l'accès aux propriétés dans les sous-classes.

L'accès aux champs n'est pas une option dans une application avec des super-classes génériques abstraites.

37
HDave

J'ai tendance à préférer et à utiliser des accesseurs:

  • Je peux ajouter une logique si le besoin s'en fait sentir (comme indiqué dans la réponse acceptée).
  • cela me permet d'appeler foo.getId()sans initialiser un proxy (important lors de l'utilisation d'Hibernate, jusqu'à ce que HHH-3718 soit résolu).

Inconvénient:

  • cela rend le code moins lisible, vous devez par exemple parcourir toute une classe pour voir s’il existe @Transient aux alentours.
33
Pascal Thivent

Cela dépend vraiment d'un cas particulier - les deux options sont disponibles pour une raison. OMI cela se résume à trois cas:

  1. setter a une logique qui ne doit pas être exécutée au moment de charger une instance depuis une base de données; par exemple, une certaine valeur est validée dans le setter, mais les données provenant de la base de données doivent être valides (sinon, elles n'y arriveraient pas (:); dans ce cas, l'accès au champ est le plus approprié;
  2. setter a une logique qui devrait toujours être invoquée, même pendant le chargement d'une instance depuis db; par exemple, la propriété en cours d'initialisation est utilisée dans le calcul d'un champ calculé (par exemple, propriété - un montant monétaire, propriété calculée - un total de plusieurs propriétés monétaires d'une même instance); dans ce cas, l'accès à la propriété est requis.
  3. Aucun des cas ci-dessus - alors les deux options sont applicables, restez simplement cohérent (par exemple, si l'accès au champ est le choix dans cette situation, utilisez-le tout le temps dans une situation similaire).
13
01es

Je recommanderais fortement l’accès aux champs et NON aux annotations sur les accesseurs (accès aux propriétés) si vous voulez faire autre chose dans les setters que de simplement définir la valeur (par exemple, le chiffrement ou le calcul). 

Le problème avec l'accès à la propriété est que les setters sont également appelés lorsque l'objet est chargé. Cela a bien fonctionné pour moi pendant de nombreux mois jusqu'à ce que nous voulions introduire le cryptage. Dans notre cas d'utilisation, nous voulions chiffrer un champ dans le séparateur et le déchiffrer dans le getter. Le problème avec l’accès aux propriétés était que lorsque Hibernate chargeait l’objet, il appelait également le configurateur pour renseigner le champ et chiffrait à nouveau la valeur chiffrée. Ce message mentionne également ceci: Java Hibernate: comportement de la fonction de jeu de propriétés différent en fonction de l'appelant

Cela m'a causé des maux de tête jusqu'à ce que je me souvienne de la différence entre l'accès aux champs et l'accès aux propriétés. Maintenant, j'ai déplacé toutes mes annotations d'accès de propriété à d'accès de champ et cela fonctionne bien maintenant. 

12
Christoph

Je préfère utiliser l'accès aux champs pour les raisons suivantes:

  1. Le property access peut conduire à de très vilains bugs lors de l'implémentation de equals/hashCode et référençant directement les champs (par opposition à leurs getters). En effet, le proxy n'est initialisé que lorsque les accesseurs sont accessibles et un accès direct au champ renverrait simplement null.

  2. Pour accéder à property, vous devez annoter toutes les méthodes utilitaires } (par exemple, addChild/removeChild) sous la forme @Transient.

  3. Avec l'accès au champ, nous pouvons masquer le champ @Version en n'exposant pas du tout un getter. Un getter peut également conduire à l'ajout d'un setter, et le champ version ne doit jamais être défini manuellement (ce qui peut entraîner des problèmes très graves). Toute incrémentation de version doit être déclenchée via OPTIMISTIC_FORCE_INCREMENT ou PESSIMISTIC_FORCE_INCREMENT _ verrouillage explicite.

8
Vlad Mihalcea

Je pense que l'annotation de la propriété est préférable car la mise à jour des champs casse directement l'encapsulation, même lorsque votre ORM le fait.

Voici un excellent exemple de l'endroit où il va vous brûler: vous voulez probablement que vos annotations pour le validateur et la persistance en veille prolongée se trouvent au même endroit (champs ou propriétés). Si vous souhaitez tester vos validations alimentées par un validateur Hibernate annotées sur un champ, vous ne pouvez pas utiliser une maquette de votre entité pour isoler votre test unitaire au validateur. Aie.

7
Justin Standard

Je crois que l'accès aux propriétés et l'accès aux champs sont légèrement différents en ce qui concerne l'initialisation paresseuse.

Considérez les correspondances suivantes pour 2 haricots de base:

<hibernate-mapping package="org.nkl.model" default-access="field">
  <class name="FieldBean" table="FIELD_BEAN">
    <id name="id">
      <generator class="sequence" />
    </id>
    <property name="message" />
  </class>
</hibernate-mapping>

<hibernate-mapping package="org.nkl.model" default-access="property">
  <class name="PropBean" table="PROP_BEAN">
    <id name="id">
      <generator class="sequence" />
    </id>
    <property name="message" />
  </class>
</hibernate-mapping>

Et les tests unitaires suivants:

@Test
public void testFieldBean() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    FieldBean fb = new FieldBean("field");
    Long id = (Long) session.save(fb);
    tx.commit();
    session.close();

    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    fb = (FieldBean) session.load(FieldBean.class, id);
    System.out.println(fb.getId());
    tx.commit();
    session.close();
}

@Test
public void testPropBean() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    PropBean pb = new PropBean("prop");
    Long id = (Long) session.save(pb);
    tx.commit();
    session.close();

    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    pb = (PropBean) session.load(PropBean.class, id);
    System.out.println(pb.getId());
    tx.commit();
    session.close();
}

Vous verrez la différence subtile dans les sélections requises:

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        FIELD_BEAN
        (message, id) 
    values
        (?, ?)
Hibernate: 
    select
        fieldbean0_.id as id1_0_,
        fieldbean0_.message as message1_0_ 
    from
        FIELD_BEAN fieldbean0_ 
    where
        fieldbean0_.id=?
0
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        PROP_BEAN
        (message, id) 
    values
        (?, ?)
1

C'est-à-dire que l'appel de fb.getId() nécessite une sélection, contrairement à pb.getId().

6
toolkit

Je privilégie les accesseurs de terrain. Le code est beaucoup plus propre. Toutes les annotations peuvent être placées dans une section D'une classe et le code est beaucoup plus facile à lire. 

J'ai rencontré un autre problème avec les accesseurs de propriété: si vous avez sur votre classe des méthodes getXYZ qui ne sont PAS annotées comme étant associées à des propriétés persistantes, hibernate génère sql pour tenter d'obtenir ces propriétés, ce qui génère des messages d'erreur très confus. Deux heures perdues. Je n'ai pas écrit ce code; J'ai toujours utilisé des accessoires de terrain par le passé et je n'ai jamais rencontré ce problème.

Les versions d'Hibernate utilisées dans cette application:

<!-- hibernate -->
<hibernate-core.version>3.3.2.GA</hibernate-core.version>
<hibernate-annotations.version>3.4.0.GA</hibernate-annotations.version>
<hibernate-commons-annotations.version>3.1.0.GA</hibernate-commons-annotations.version>
<hibernate-entitymanager.version>3.4.0.GA</hibernate-entitymanager.version>
2
John Goyer

Sommes-nous déjà là

C'est une présentation ancienne, mais Rod suggère que l'annotation sur l'accès aux propriétés encourage les modèles de domaine anémiques et ne devrait pas être le moyen "par défaut" d'annoter.

2
Manuel Palacio

Je préfère les champs, mais j'ai rencontré une situation qui semble m'obliger à placer les annotations sur des accesseurs.

Avec l'implémentation Hibernate JPA, @Embedded ne semble pas fonctionner sur les champs. Donc, cela doit aller sur le getter. Et une fois que vous avez mis cela sur le getter, les différentes annotations @Column doivent également aller sur les accesseurs. (Je pense que Hibernate ne veut pas mélanger les getters et les champs ici.) Et une fois que vous mettez @Column sur les getters dans une classe, il est probablement logique de le faire partout.

2
Willie Wheeler

Par défaut, les fournisseurs JPA accèdent aux valeurs des champs d’entité et les mappent aux colonnes de la base de données À l’aide des méthodes d’accesseur de propriété JavaBean (getter) et de mutateur (setter). En tant que tel, les noms et les types Des champs privés d'une entité n'ont pas d'importance pour JPA. Au lieu de cela, JPA examine uniquement Les noms et les types de retour des accesseurs de propriété JavaBean. Vous pouvez modifier cela à l'aide de l'annotation @javax.persistence.Access, ce qui vous permet de spécifier explicitement la méthode d'accès Que le fournisseur JPA doit employer.

@Entity
@Access(AccessType.FIELD)
public class SomeEntity implements Serializable
{
...
}

Les options disponibles pour l'énumération AccessType sont PROPERTY (valeur par défaut) et FIELD. AvecPROPERTY, le fournisseur obtient et définit les valeurs de champ à l'aide des méthodes de la propriété JavaBean. FIELD permet au fournisseur d'obtenir et de définir les valeurs de champ à l'aide des champs d'instance. Il est préférable de collerà la valeur par défaut et d'utiliser les propriétés JavaBean à moins que vous n'ayez une raison impérieuse de procéder autrement.

Vous pouvez placer ces annotations de propriété sur les champs privés ou sur les méthodes d'accès public. Si vous utilisez AccessType.PROPERTY (valeur par défaut) et annotez les champs privés au lieu des accesseurs JavaBean , Les noms de champ doivent correspondre aux noms de propriété JavaBean. Cependant, les noms ne doivent pas nécessairement correspondre si vous annotez les accesseurs JavaBean. De même, si vous utilisez AccessType.FIELD et Annotez les accesseurs JavaBean au lieu des champs, les noms de champ doivent également correspondre aux noms JavaBeanproperty. Dans ce cas, ils ne doivent pas nécessairement correspondre si vous annotez les champs. Il est préférable d’harmoniser et d’annoter les accesseurs JavaBean pour AccessType.PROPERTY et les champs pour AccessType.FIELD.

Il est important de ne jamais mélanger les annotations de propriété JPA et les annotations de champ JPA dans la même entité. Cela entraîne un comportement non spécifié et est très susceptible de provoquer des erreurs.

2
Faraz Durrani

Un autre point en faveur de l'accès aux champs est que sinon, vous êtes obligé d'exposer les setters pour les collections, ce qui est une mauvaise idée pour moi, car changer l'instance de collection persistante en un objet non géré par Hibernate va définitivement altérer la cohérence de vos données.

Je préfère donc initialiser les collections en tant que champs protégés pour vider les implémentations dans le constructeur par défaut et n’exposer que leurs getters. Ensuite, seules les opérations gérées, telles que clear(), remove(), removeAll(), etc., sont possibles et ne permettront jamais à Hibernate d'ignorer les modifications.

2
Jiří Vypědřík

Vous devez choisir l’accès via les champs d’accès via les propriétés . Avec les champs vous pouvez limiter les données envoyées et reçues . Avec les propriétés, vous pouvez envoyer plus de données en tant qu’hôte, et Définir les dénominations G (quelle définir la plupart des propriétés au total).

2
user10801787

J'ai résolu l'initialisation paresseuse et l'accès aux champs ici Hibernate one-to-one: getId () sans récupérer l'objet entier

1
xmedeko

J'ai eu la même question concernant accesstype en veille prolongée et trouvé quelques réponses ici .

1
sundary

Nous avons créé des beans entity et utilisé des annotations de getter. Le problème que nous avons rencontré est le suivant: certaines entités ont des règles complexes pour certaines propriétés en ce qui concerne le moment où elles peuvent être mises à jour. La solution consistait à avoir dans chaque programme de configuration une logique métier qui détermine si la valeur réelle a changé ou non et, le cas échéant, si la modification doit être autorisée. Bien entendu, Hibernate peut toujours définir les propriétés. Nous nous sommes donc retrouvés avec deux groupes de setters. Assez laid.

En lisant les articles précédents, je constate également que la référence aux propriétés à l'intérieur de l'entité peut entraîner des problèmes de non-chargement des collections.

En bout de ligne, je me pencherais pour annoter les champs à l'avenir.

1
Steve11235

je pense à ce sujet et je choisis la méthode d'accès 

pourquoi? 

parce que field et methos accesor sont les mêmes mais si plus tard j'ai besoin d'une certaine logique dans le champ de chargement, je sauvegarde le déplacement de toutes les annotations placées dans les champs

cordialement

Grubhart

0
user261548

Normalement, les haricots sont des POJO, ils ont donc des accesseurs. 

La question n'est donc pas "lequel est le meilleur?", Mais simplement "quand utiliser l'accès au champ?". Et la réponse est "quand vous n'avez pas besoin d'un passeur/getter pour le terrain!".

0
Vladimir Dyuzhev

Permettez-moi de tenter de résumer les raisons les plus importantes pour choisir un accès basé sur le champ. Si vous souhaitez approfondir, veuillez lire cet article sur mon blog: Stratégies d'accès dans JPA et Hibernate - Quel est le meilleur accès aux champs ou aux propriétés?

L'accès basé sur le champ est de loin la meilleure option. Voici 5 raisons pour cela:

Raison 1: Meilleure lisibilité de votre code

Si vous utilisez un accès basé sur le champ, vous annotez vos attributs d'entité avec vos annotations de mappage. En plaçant la définition de tous les attributs d'entité en haut de votre classe, vous obtenez une vue relativement compacte de tous les attributs et de leurs mappages.

Reason 2: Omettez les méthodes getter ou setter qui ne devraient pas être appelées par votre application

Un autre avantage de l’accès basé sur le champ est que votre fournisseur de persistance, par exemple Hibernate ou EclipseLink, n’utilise pas les méthodes d’obtention et de définition de vos attributs d’entité. Cela signifie que vous n'avez pas besoin de fournir une méthode qui ne devrait pas être utilisée par votre code d'entreprise. C'est le cas le plus souvent pour les méthodes de définition de attributs de clé primaire générés ou des colonnes de version. Votre fournisseur de persistance gère les valeurs de ces attributs et vous ne devez pas les définir par programme.

Raison 3: implémentation flexible des méthodes getter et setter

Étant donné que votre fournisseur de persistance n'appelle pas les méthodes getter et setter, elles ne sont pas obligées de satisfaire aux exigences externes. Vous pouvez implémenter ces méthodes comme vous le souhaitez. Cela vous permet d'implémenter des règles de validation spécifiques à l'entreprise, de déclencher une logique d'entreprise supplémentaire ou de convertir l'attribut d'entité en un type de données différent.

Vous pouvez, par exemple, utiliser cela pour encapsuler une association ou un attribut optionnel dans un fichier Java Optional.

Reason 4: Inutile de marquer les méthodes utilitaires avec @Transient

Un autre avantage de la stratégie d’accès basée sur le champ est qu’il n’est pas nécessaire d’annoter vos méthodes utilitaires avec @Transient. Cette annotation indique à votre fournisseur de persistance qu'une méthode ou un attribut ne fait pas partie de l'état persistant de l'entité. Et comme avec l'accès de type champ, l'état persistant est défini par les attributs de votre entité, votre implémentation JPA ignore toutes les méthodes de votre entité.

Raison 5: Évitez les bugs lorsque vous travaillez avec des mandataires

Hibernate utilise des proxies pour associations paresseuses à l'extrême afin de pouvoir contrôler l'initialisation de ces associations. Cette approche fonctionne bien dans presque toutes les situations. Mais cela crée un piège dangereux si vous utilisez un accès basé sur les propriétés.

Si vous utilisez un accès basé sur une propriété, Hibernate initialise les attributs de l'objet proxy lorsque vous appelez la méthode getter. C’est toujours le cas si vous utilisez l’objet proxy dans votre code d’entreprise. Mais beaucoup de implémentations de equals et hashCode accèdent directement aux attributs. S'il s'agit de la première fois que vous accédez à l'un des attributs de proxy, ces attributs ne sont toujours pas initialisés.

0
Thorben Janssen

Pour rendre vos classes plus propres, placez l'annotation dans le champ puis utilisez @Access (AccessType.PROPERTY)

0
ely

Tous les deux : 

La spécification EJB3 nécessite que vous déclariez des annotations sur l'élément type auquel on accédera, c’est-à-dire la méthode getter si vous utilisez la propriété access, le champ si vous utilisez l'accès au champ.

https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping

0
moueza

AccessType.PROPERTY: L'implémentation de persistance EJB chargera l'état dans votre classe via les méthodes "setter" JavaBean et récupérera l'état de votre classe à l'aide des méthodes "getter" JavaBean. C'est la valeur par défaut.

AccessType.FIELD: State est chargé et extrait directement des champs de votre classe. Vous n'êtes pas obligé d'écrire JavaBean "getters" et "setters".

0
Vicky