Hibernate Annotations – Quel est le meilleur, access aux champs ou aux propriétés?

Cette question est quelque peu liée à la question de placement Hibernate Annotation .

Mais je veux savoir quel est le meilleur ? Accès via propriétés ou access via des champs? Quels sont les avantages et les inconvénients de chacun?

Je préfère les accesseurs, car je peux append une logique métier à mes accesseurs chaque fois que j’en ai besoin. Voici un exemple:

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

De plus, si vous lancez une autre lib dans le mixage (comme certains lib libs JSON ou BeanMapper ou Dozer ou autre lib de bean mapping / cloning basé sur les propriétés getter / setter), vous aurez la garantie que la lib est en phase avec la persistance manager (les deux utilisent le getter / setter).

Il y a des arguments pour les deux, mais la plupart d’entre eux proviennent de certaines exigences de l’utilisateur “et si vous devez append de la logique pour”, ou “xxxx rompt l’encapsulation”. Cependant, personne n’a vraiment commenté la théorie, et a donné un argument correctement motivé.

Qu’est-ce que Hibernate / JPA fait réellement lorsqu’il persiste un object – eh bien, il persiste l’ETAT de l’object. Cela signifie le stocker de manière à pouvoir le reproduire facilement.

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

Pensez à ceci 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 de clavier, etc. Cependant, lorsque vous choisissez de conserver (Enregistrer) ce document, il enregistre l’état interne, pas l’ensemble des pressions clics de souris utilisés pour le générer.

L’enregistrement de l’état interne de l’object ne rompt PAS l’encapsulation – sinon vous ne comprenez pas vraiment ce que signifie l’encapsulation et pourquoi elle existe. C’est comme la sérialisation des objects.

Pour cette raison, DANS LA PLUPART DES CAS, il convient de conserver les ZONES et non les ACCESSOIRES. Cela signifie qu’un object peut être recréé avec précision à partir de la firebase database exactement comme il a été stocké. Il ne devrait pas nécessiter de validation, car cela a été fait sur l’original lors de sa création et avant qu’il ne soit stocké dans la firebase database (sauf si, Dieu nous en préserve, vous stockez des données non valides dans la firebase database !!!!). De même, il ne devrait pas être nécessaire de calculer des valeurs, car elles étaient déjà calculées avant le stockage de l’object. L’object doit ressembler à ce qu’il a fait avant d’être enregistré. En fait, en ajoutant des éléments supplémentaires aux getters / setters, vous augmentez 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 valables pour persister les accesseurs, cependant, ils seront généralement rares. Un exemple peut être que vous voulez éviter de conserver une valeur calculée, bien que vous souhaitiez peut-être poser la question de savoir pourquoi vous ne la calculez pas à la demande dans le getter de la valeur ou l’initialisez paresseusement dans le getter. Personnellement, je ne peux pas penser à un bon cas d’utilisation, et aucune des réponses ne donne vraiment une réponse “Software Engineering”.

Je préfère l’access aux champs, car je ne suis pas obligé de fournir getter / setter pour chaque propriété.

Une enquête rapide via Google suggère que l’access sur le terrain est la majorité (par exemple, http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype ).

Je crois que l’access sur le terrain est l’idiome recommandé par Spring, mais je ne trouve pas de référence pour le sauvegarder.

Il y a une question connexe de SO qui a tenté de mesurer la performance et qui est arrivée à la conclusion qu’il n’y avait “aucune différence”.

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

 public abstract class Foo { T oneThing; T anotherThing; // getters and setters ommited for brevity // Lots and lots of implementation regarding oneThing and anotherThing here } 

Maintenant, comment annoter exactement cette classe? La réponse est que VOUS NE POUVEZ PAS l’annoter avec l’access aux champs ou aux propriétés 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’access à la propriété dans les sous-classes.

L’access aux champs n’est pas une option dans une application avec des super-classes génériques abstraites.

J’ai tendance à préférer et à utiliser des accesseurs de propriété:

  • Je peux append une logique si le besoin s’en fait sentir (comme mentionné 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 une classe entière pour voir s’il y a des @Transient .

Cela dépend vraiment d’un cas spécifique – les deux options sont disponibles pour une raison. L’OMI se résume à trois cas:

  1. setter a une logique qui ne devrait pas être exécutée au moment du chargement d’une instance à partir d’une firebase database; Par exemple, une validation de valeur se produit dans le setter, cependant les données provenant de db devraient être valides (sinon, elles ne seraient pas disponibles (:); dans ce cas, l’access 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 à partir de la firebase database; Par exemple, la propriété en cours d’initialisation est utilisée dans le calcul d’un champ calculé (par exemple, une propriété – un montant monétaire, une propriété calculée – un total de plusieurs propriétés monétaires de la même instance); dans ce cas, l’access à la propriété est requirejs.
  3. Aucun des cas ci-dessus – alors les deux options sont applicables, restz juste cohérent (ei si l’access au champ est le choix dans cette situation, alors utilisez-le tout le temps dans une situation similaire).

Je recommande fortement l’access aux champs et NON aux annotations sur les getters (access aux propriétés) si vous voulez faire plus dans les parameters que de simplement définir la valeur (par exemple, cryptage ou calcul).

Le problème avec l’access à la propriété est que les setters sont également appelés lorsque l’object est chargé. Cela a fonctionné pour moi bien pendant plusieurs mois jusqu’à ce que nous voulions introduire le chiffrement. Dans notre cas d’utilisation, nous voulions chiffrer un champ dans le setter et le déchiffrer dans le getter. Le problème maintenant avec l’access à la propriété était que lorsque Hibernate chargeait l’object, il appelait aussi le setter pour remplir le champ et chiffrait ainsi à nouveau la valeur chiffrée. Cet article mentionne également ceci: Java Hibernate: le comportement d’une fonction de jeu de propriétés différent dépend de qui l’appelle

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

Je pense qu’il est préférable d’annoter la propriété car la mise à jour des champs annule directement l’encapsulation, même si votre ORM le fait.

Voici un bon exemple de l’endroit où il va vous graver: vous voulez probablement vos annotations pour le validateur et la persistance d’hibernation au même endroit (champs ou propriétés). Si vous souhaitez tester vos validations alimentées par un validateur d’hibernation qui sont annotées sur un champ, vous ne pouvez pas utiliser une maquette de votre entité pour isoler votre test unitaire uniquement avec le validateur. Aie.

Je crois que l’access à la propriété par rapport à l’access sur le terrain est subtilement différent en ce qui concerne l’initialisation paresseuse.

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

                 

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 requirejses:

 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 

En d’autres fb.getId() , l’appel de fb.getId() nécessite une sélection, contrairement à pb.getId() .

Je préfère utiliser l’access sur le terrain pour les raisons suivantes:

  1. L’ access à la propriété peut conduire à des bogues très désagréables lors de la mise en œuvre des champs equals / hashCode et de référencement direct (par opposition à leurs getters). Cela est dû au fait que le proxy est uniquement initialisé lors de l’access aux getters, et qu’un access direct à un champ renvoie simplement null.

  2. L’ access à la propriété nécessite que vous annotiez toutes les méthodes d’utilitaire (par exemple, addChild / removeChild) en tant que @Transient .

  3. Avec l’access aux champs, 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 de version ne doit jamais être défini manuellement (ce qui peut entraîner des problèmes très graves). Toutes les incrémentations de version doivent être déclenchées par le locking explicite OPTIMISTIC_FORCE_INCREMENT ou PESSIMISTIC_FORCE_INCREMENT .

Sommes-nous déjà là

C’est une vieille présentation mais Rod suggère que l’annotation sur l’access à la propriété encourage les modèles de domaine anémiques et ne devrait pas être la manière “par défaut” d’annoter.

Un autre point en faveur de l’access sur le terrain est que sinon vous êtes obligé d’exposer les parameters pour les collections, ce qui est une mauvaise idée pour moi car changer l’instance de la collection persistante en un object non géré par Hibernate va certainement briser la cohérence des données.

Je préfère donc avoir des collections comme champs protégés initialisés pour vider les implémentations dans le constructeur par défaut et exposer uniquement leurs getters. Ensuite, seules les opérations gérées telles que clear() , remove() , removeAll() etc. sont possibles et ne feront jamais que Hibernate ignore les modifications.

Je préfère les champs, mais je suis tombé sur une situation qui semble me forcer à placer les annotations sur les getters.

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

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 trouvé un autre problème avec les accesseurs de propriété: si vous avez des méthodes getXYZ sur votre classe qui ne sont pas annotées comme associées à des propriétés persistantes, hibernate génère sql pour tenter d’obtenir ces propriétés, entraînant des messages d’erreur très déroutants. Deux heures perdues. Je n’ai pas écrit ce code; J’ai toujours utilisé des accesseurs sur le terrain par le passé et je n’ai jamais rencontré ce problème.

Versions d’Hibernate utilisées dans cette application:

  3.3.2.GA 3.4.0.GA 3.1.0.GA 3.4.0.GA 

J’ai eu la même question concernant le type d’access en mode veille prolongée et j’ai trouvé des réponses ici .

J’ai résolu l’initialisation paresseuse et l’access aux champs ici Hibernate one-to-one: getId () sans récupérer l’object entier

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

En lisant les articles précédents, je constate également que le fait de référencer les propriétés à l’intérieur de l’entité pourrait entraîner des problèmes de chargement des collections.

En bout de ligne, je pencherais vers l’annotation des champs à l’avenir.

Par défaut, les fournisseurs JPA accèdent aux valeurs des champs d’entité et mappent ces champs aux colonnes de la firebase database en utilisant les méthodes d’access à la propriété JavaBean (getter) et mutator (setter) de l’entité. En tant que tels, les noms et types de 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és JavaBean. Vous pouvez modifier cela à l’aide de l’annotation @javax.persistence.Access , qui vous permet de spécifier explicitement la méthodologie d’access que le fournisseur JPA doit utiliser.

 @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. Avec PROPERTY, le fournisseur obtient et définit des valeurs de champ à l’aide des méthodes de propriété JavaBean. FIELD oblige le fournisseur à obtenir et à définir des valeurs de champ à l’aide des champs d’instance. En guise de meilleure pratique, vous devez simplement respecter la valeur par défaut et utiliser les propriétés JavaBean, sauf si vous avez des raisons impérieuses de faire autrement.

Vous pouvez placer ces annotations de propriétés sur les méthodes privées ou publiques. Si vous utilisez AccessType.PROPERTY (par défaut) et AccessType.PROPERTY les champs privés au lieu des accesseurs JavaBean, les noms de champs doivent correspondre aux noms de propriétés JavaBean. Toutefois, les noms ne doivent pas nécessairement correspondre si vous annotez les accesseurs JavaBean. De même, si vous utilisez AccessType.FIELD et AccessType.FIELD les accesseurs JavaBean au lieu des champs, les noms de champs doivent également correspondre aux noms de propriétés JavaBean. Dans ce cas, ils ne doivent pas correspondre si vous annotez les champs. Il est préférable d’être cohérent 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és JPA et les annotations de champs JPA dans la même entité. Cela entraîne un comportement non spécifié et est très susceptible de provoquer des erreurs.

Normalement, les haricots sont des POJO, donc ils ont des accesseurs quand même.

Donc, la question n’est pas “lequel est le meilleur?”, Mais simplement “quand utiliser l’access aux champs?”. Et la réponse est “quand vous n’avez pas besoin d’un setter / getter pour le terrain!”.

Je pense à cela et je choisis l’accesseur de méthode

Pourquoi?

parce que le champ et le methos accesor sont les mêmes mais si plus tard j’ai besoin de logique dans le champ de charge, je sauve le déplacement de toutes les annotations placées dans les champs

Cordialement

Grubhart

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

Tous les deux :

La spécification EJB3 exige que vous déclariez des annotations sur le type d’élément auquel vous aurez access, c’est-à-dire la méthode getter si vous utilisez la propriété access, le champ si vous utilisez l’access aux champs.

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

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. Ceci est la valeur par défaut.

AccessType.FIELD: L’état est chargé et récupéré directement à partir des champs de votre classe. Vous n’avez pas besoin d’écrire JavaBean “getters” et “setters”.