Cette question est quelque peu liée à la question de placement d'annotation Hibernate .
Mais je veux savoir ce qui est le mieux ? Accès via les propriétés ou accès via les champs? Quels sont les avantages et les inconvénients de chacun?
Cette question est quelque peu liée à la question de placement d'annotation Hibernate .
Mais je veux savoir ce qui est le mieux ? Accès via les propriétés ou accès via les champs? Quels sont les avantages et les inconvénients de chacun?
Réponses:
Je préfère les accesseurs, car je peux ajouter une logique métier à mes accesseurs quand j'en ai besoin. Voici un exemple:
@Entity
public class Person {
@Column("nickName")
public String getNickName(){
if(this.name != null) return generateFunnyNick(this.name);
else return "John Doe";
}
}
De plus, si vous lancez une autre bibliothèque dans le mix (comme une bibliothèque de conversion JSON ou BeanMapper ou Dozer ou une autre bibliothèque de mappage / clonage de bean basée sur les propriétés getter / setter), vous aurez la garantie que la bibliothèque est synchronisée 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 avez besoin d'ajouter une logique pour", ou "xxxx interrompt l'encapsulation". Cependant, personne n'a vraiment commenté la théorie et donné un argument correctement motivé.
Que fait réellement Hibernate / JPA quand il persiste un objet - eh bien, il persiste dans l'état de l'objet. Cela signifie le stocker de manière à pouvoir être facilement reproduit.
Qu'est-ce que l'encapsulation? Les encapsulations consistent à 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 maintient 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 clavier, etc. Cependant, lorsque vous choisissez de conserver (Enregistrer) ce document, il enregistre l'état interne, pas l'ensemble des touches et 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 elle existe. C'est exactement comme la sérialisation d'objets.
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é. Il ne devrait avoir besoin d'aucune validation, car cela a été fait sur l'original lors de sa création, et avant qu'il ne soit stocké dans la base de données (sauf si, Dieu nous en préserve, vous stockez des données invalides dans la base de données !!!!). De même, il ne devrait pas être nécessaire de calculer les valeurs, car elles ont déjà été calculées avant le stockage de l'objet. L'objet doit ressembler à ce qu'il était avant d'être enregistré. En fait, en ajoutant des éléments supplémentaires dans les getters / setters, vous augmentez en fait 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 la persistance des accesseurs, mais ils seront généralement rares. Un exemple peut être que vous souhaitez éviter de conserver une valeur calculée, bien que vous souhaitiez peut-être demander 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 penser à aucun bon cas d'utilisation, et aucune des réponses ici ne donne vraiment une réponse "Génie logiciel".
Je préfère l'accès aux champs, car de cette façon je ne suis pas obligé de fournir un getter / setter pour chaque propriété.
Une enquête rapide via Google suggère que l'accès sur le terrain est majoritaire (par exemple, http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype ).
Je crois que l'accès au champ est l'idiome recommandé par Spring, mais je ne trouve pas de référence pour le confirmer.
Il y a une question relative à l'OS qui a tenté de mesurer le rendement et qui est parvenue à la conclusion qu'il n'y avait "aucune différence".
Voici une situation dans laquelle vous DEVEZ utiliser des accesseurs de propriété. Imaginez que vous ayez une classe abstraite GENERIC avec beaucoup de qualités d'implémentation à hériter en 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 devez-vous annoter cette classe? La réponse est que VOUS NE POUVEZ PAS l'annoter du tout avec un accès au champ ou à la propriété car vous ne pouvez pas spécifier l'entité cible à ce stade. Vous DEVEZ annoter les implémentations concrètes. Mais puisque 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.
abstract T getOneThing()
, et abstract void setOneThing(T thing)
, et utiliser l'accès aux champs.
J'ai tendance à préférer et à utiliser des accesseurs de propriété:
foo.getId()
sans initialiser un proxy (important lors de l'utilisation de Hibernate, jusqu'à ce que HHH-3718 soit résolu).Inconvénient:
@Transient
par là.Cela dépend vraiment d'un cas spécifique - les deux options sont disponibles pour une raison. OMI, cela se résume à trois cas:
Je recommanderais fortement l'accès au champ et PAS les annotations sur les getters (accès à la propriété) si vous voulez faire autre chose dans les setters que de simplement définir la valeur (par exemple, chiffrement ou 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 plusieurs mois jusqu'à ce que nous voulions introduire le cryptage. Dans notre cas d'utilisation, nous voulions crypter un champ dans le setter et le décrypter dans le getter. Le problème maintenant avec l'accès aux propriétés était que lorsque Hibernate chargeait l'objet, il appelait également l'installateur pour remplir le champ et chiffrait donc à nouveau la valeur cryptée. Cet article 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 au terrain et l'accès à la propriété. Maintenant, j'ai déplacé toutes mes annotations de l'accès à la propriété à l'accès au champ et cela fonctionne bien maintenant.
Je préfère utiliser l'accès aux champs pour les raisons suivantes:
L' accès à la propriété peut conduire à des bugs très désagréables lors de l'implémentation de equals / hashCode et du référencement des champs directement (par opposition à leurs getters). En effet, le proxy n'est initialisé que lors de l'accès aux getters, et un accès direct par champ renverrait simplement null.
L' accès aux propriétés vous oblige à annoter toutes les méthodes utilitaires (par exemple, addChild / removeChild) comme @Transient
.
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 version
champ ne doit jamais être défini manuellement (ce qui peut conduire à des problèmes très désagréables). Toute incrémentation de version doit être déclenchée via le verrouillage explicite OPTIMISTIC_FORCE_INCREMENT ou PESSIMISTIC_FORCE_INCREMENT .
version
champ est souvent utile dans les situations où les DTO sont utilisés à la place d'entités détachées.
Je pense que l'annotation de la propriété est meilleure car la mise à jour des champs rompt directement l'encapsulation, même lorsque votre ORM le fait.
Voici un excellent exemple de l'endroit où cela vous brûlera: vous voulez probablement que vos annotations pour le validateur d'hibernation et la persistance au même endroit (champs ou propriétés). Si vous souhaitez tester vos validations alimentées par le validateur de mise en veille prolongée qui sont annotées sur un champ, vous ne pouvez pas utiliser une maquette de votre entité pour isoler votre test unitaire au seul validateur. Aie.
Je crois que l'accès à la propriété et l'accès au champ sont légèrement différents en ce qui concerne l'initialisation paresseuse.
Considérez les mappages suivants pour 2 beans 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
Autrement dit, l'appel fb.getId()
nécessite un select, alors que ce pb.getId()
n'est pas le cas.
Permettez-moi d'essayer de résumer les raisons les plus importantes du choix de l'accès basé sur le terrain. Si vous souhaitez approfondir, veuillez lire cet article sur mon blog: Stratégies d'accès dans JPA et Hibernate - Qu'est-ce qui est le meilleur, l'accès au champ ou à la propriété?
L'accès sur le terrain est de loin la meilleure option. Voici 5 raisons à cela:
Raison 1: Meilleure lisibilité de votre code
Si vous utilisez l'accès basé sur les champs, 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.
Raison 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 les champs est que votre fournisseur de persistance, par exemple Hibernate ou EclipseLink, n'utilise pas les méthodes getter et setter de vos attributs d'entité. Cela signifie que vous n'avez pas besoin de fournir de méthode qui ne devrait pas être utilisée par votre code d'entreprise. C'est le plus souvent le cas pour les méthodes de définition des 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, ils ne sont pas obligés de répondre à des exigences externes. Vous pouvez implémenter ces méthodes comme vous le souhaitez. Cela vous permet d'implémenter des règles de validation métier, de déclencher une logique métier supplémentaire ou de convertir l'attribut d'entité en un type de données différent.
Vous pouvez, par exemple, l'utiliser pour envelopper une association ou un attribut facultatif dans un Java Optional
.
Raison 4: Pas besoin de marquer les méthodes utilitaires comme @Transient
Un autre avantage de la stratégie d'accès basée sur le terrain est que vous n'avez pas besoin 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: éviter les bogues lorsque vous travaillez avec des proxys
Hibernate utilise des proxys pour les associations récupérées paresseusement vers un afin de pouvoir contrôler l'initialisation de ces associations. Cette approche fonctionne bien dans presque toutes les situations. Mais cela présente un piège dangereux si vous utilisez un accès basé sur la propriété.
Si vous utilisez un accès basé sur les propriétés, 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 métier. Mais un grand nombre d' implémentations d' equals et de hashCode accèdent directement aux attributs. Si c'est la première fois que vous accédez à l'un des attributs de proxy, ces attributs ne sont toujours pas initialisés.
Par défaut, les fournisseurs JPA accèdent aux valeurs des champs d'entité et mappent ces champs aux colonnes de base de données à l'aide des méthodes d'accès aux propriétés JavaBean (getter) et mutator (setter) de l'entité. En tant que tels, les noms et les types des champs privés dans 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 le modifier à l'aide de l' @javax.persistence.Access
annotation, qui vous permet de spécifier explicitement la méthodologie d'accès 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 les valeurs de champ à l'aide des méthodes de propriété JavaBean. FIELD permet au fournisseur d'obtenir et de définir des valeurs de champ à l'aide des champs d'instance. En tant que meilleure pratique, vous devez simplement vous en tenir à la valeur par défaut et utiliser les propriétés JavaBean, sauf si vous avez une raison impérieuse de faire 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
(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 de propriété JavaBean. Dans ce cas, ils ne doivent pas nécessairement correspondre si vous annotez les champs. Il est préférable d'être cohérent et d'annoter les accesseurs JavaBean 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.
C'est une vieille présentation, 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 la manière «par défaut» d'annoter.
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, pour moi, est une mauvaise idée car changer l'instance de collection persistante en un objet non géré par Hibernate rompra définitivement la cohérence de vos données.
Je préfère donc avoir des collections en tant que champs protégés initialisés à des implémentations vides dans le constructeur par défaut et n'exposer que leurs getters. Ensuite, seules les opérations gérées comme clear()
, remove()
, removeAll()
etc sont possibles qui ne connaissent pas faire Hibernate changements.
Je préfère les champs, mais j'ai rencontré 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, cela doit aller sur le getter. Et une fois que vous avez mis cela sur le getter, les différentes @Column
annotations doivent également aller sur les getters. (Je pense qu'Hibernate ne veut pas mélanger les champs et les getters ici.) Et une fois que vous mettez des @Column
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 étant associées à des propriétés persistantes, hibernate génère SQL pour tenter d'obtenir ces propriétés, ce qui entraîne des messages d'erreur très déroutants. Deux heures perdues. Je n'ai pas écrit ce code; J'ai toujours utilisé des accesseurs de terrain dans le passé et je n'ai jamais rencontré ce problème.
Versions 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>
Vous devez choisir l'accès via les champs plutôt que l'accès via les propriétés. Avec les champs, vous pouvez limiter les données envoyées et reçues. Avec via les propriétés, vous pouvez envoyer plus de données en tant qu'hôte et définir des dénominations G (qui définissent en usine la plupart des propriétés au total).
J'ai eu la même question concernant l'accès en veille prolongée et j'ai trouvé des réponses ici .
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
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 était d'avoir une logique métier dans chaque setter qui détermine si la valeur réelle a changé ou non et, si tel est le cas, si la modification doit être autorisée. Bien sûr, 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 vois également que référencer les propriétés depuis 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.
Normalement, les beans sont POJO, donc ils ont quand même des accesseurs.
La question n'est donc pas "lequel est le meilleur?", Mais simplement "quand utiliser l'accès aux champs?". Et la réponse est "quand vous n'avez pas besoin d'un setter / getter pour le terrain!".
j'y pense et je choisis l'accessoire de méthode
Pourquoi?
parce que l'accesseur de champ et de methos est le même mais si plus tard j'ai besoin de logique dans le champ de chargement, j'enregistre déplacer toutes les annotations placées dans les champs
Cordialement
Grubhart
Tous les deux :
La spécification EJB3 exige que vous déclariez des annotations sur le type d'élément qui sera accédé, c'est-à-dire la méthode getter si vous utilisez l'accès aux propriétés, le champ si vous utilisez l'accès 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 JavaBean "setter" et récupérera l'état de votre classe en utilisant les méthodes "getter" JavaBean. C'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 des "getters" et des "setters" JavaBean.