web-dev-qa-db-fra.com

Faire un OneToOne-relation paresseux

Dans cette application que nous développons, nous avons remarqué qu'une vue était particulièrement lente. J'ai profilé la vue et remarqué qu'il y avait une requête exécutée par hibernate qui prenait 10 secondes même s'il n'y avait que deux objets à extraire dans la base de données. Toutes les relations OneToMany et ManyToMany étaient paresseuses, ce n'était donc pas le problème. Lors de l'inspection du code SQL en cours d'exécution, j'ai remarqué qu'il y avait plus de 80 jointures dans la requête.

En examinant plus avant le problème, j’ai remarqué que le problème était dû à la hiérarchie profonde des relations OneToOne et ManyToOne entre les classes d’entités. Alors, j'ai pensé, je vais juste les faire aller chercher paresseux, cela devrait résoudre le problème. Mais annoter @OneToOne(fetch=FetchType.LAZY) ou @ManyToOne(fetch=FetchType.LAZY) ne semble pas fonctionner. Soit je reçois une exception, soit ils ne sont pas remplacés par un objet proxy et sont donc paresseux.

Des idées comment je vais faire fonctionner ça? Notez que je n'utilise pas le persistence.xml pour définir les relations ou les détails de configuration, tout est fait dans le code Java.

200
Kim L

Tout d’abord, quelques éclaircissements à la réponse de [~ # ~] kle [~ # ~] :

  1. L'association un-à-un sans contrainte (nullable) est la seule qui ne puisse pas être mandatée sans instrumentation de code-octet. La raison en est que l’entité propriétaire DOIT savoir si la propriété d’association doit contenir un objet proxy ou une valeur NULL. Elle ne peut pas déterminer cela en consultant les colonnes de sa table de base en raison du mappage normal de un à un via une clé partagée. de toute façon ce qui rend le mandataire inutile. Voici une explication plus détaillée .

  2. les associations plusieurs-à-un (et un-à-plusieurs, évidemment) ne souffrent pas de ce problème. L'entité propriétaire peut facilement vérifier ses propres FK (et dans le cas d'un à plusieurs, un proxy de collection vide est créé initialement et rempli à la demande), ainsi l'association peut être paresseuse.

  3. Remplacer un à un par un à un n'est pas une bonne idée. Vous pouvez le remplacer par un unique plusieurs-à-un, mais il existe d'autres options (éventuellement meilleures).

Rob H. a un point valide, mais vous ne pourrez peut-être pas le mettre en œuvre en fonction de votre modèle (par exemple, si votre association un-à-un = est nullable).

Maintenant, en ce qui concerne la question initiale:

A) @ManyToOne(fetch=FetchType.LAZY) devrait fonctionner correctement. Êtes-vous sûr de ne pas écraser la requête elle-même? Il est possible de spécifier join fetch dans HQL et/ou définir explicitement le mode de récupération via l'API de critères, qui aurait priorité sur l'annotation de classe. Si ce n'est pas le cas et que vous rencontrez toujours des problèmes, publiez vos classes, votre requête et le code SQL résultant pour une conversation plus pointue.

B) @OneToOne est plus compliqué. Si ce n'est définitivement pas nullable, suivez la suggestion de Rob H. et spécifiez-la comme telle:

@OneToOne(optional = false, fetch = FetchType.LAZY)

Sinon, si vous pouvez modifier votre base de données (ajouter une colonne de clé étrangère à la table owner), faites-le et mappez-la comme "join":

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

et dans OtherEntity:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

Si vous ne pouvez pas faire cela (et ne pouvez pas vivre avec une extraction désirée), l’instrumentation en bytecode est votre seule option. Je suis d'accord avec CPerkins , cependant - si vous avez 80 !!! se joint en raison d'associations OneToOne impatientes, vous avez de plus gros problèmes que celui-ci :-)

202
ChssPly76

Pour que le chargement paresseux fonctionne sur les mappages un à un nullables, vous devez laisser hibernate faire compiler une instrumentation et ajouter un @LazyToOne(value = LazyToOneOption.NO_PROXY) à la relation un à un.

Exemple de mappage:

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

Exemple d’extension de fichier Ant Build (pour effectuer l’instrumentation de compilation Hibernate):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>
18
Kdeveloper

L'idée de base des XToOnes dans Hibernate est qu'ils ne sont pas paresseux dans la plupart des cas.

Une des raisons est que, quand Hibernate doit décider de mettre un proxy (avec l'identifiant) ou un null,
il faut quand même regarder dans l'autre table pour rejoindre. Le coût d’accès à l’autre table de la base de données étant considérable, il convient également de récupérer les données de cette table à ce moment-là (comportement non paresseux), au lieu de rechercher dans une demande ultérieure nécessitant un deuxième accès à la base de données. même table.

Édité: pour plus de détails, veuillez vous référer à la réponse de ChssPly76. Celui-ci est moins précis et détaillé, il n'a rien à offrir. Merci ChssPly76.

10
KLE

Voici quelque chose qui a fonctionné pour moi (sans instrumentation):

Au lieu d'utiliser @OneToOne Des deux côtés, j'utilise @OneToMany Dans la partie inverse de la relation (celle avec mappedBy). Cela fait de la propriété une collection (List dans l'exemple ci-dessous), mais je la traduis en un élément du getter, la rendant transparente pour les clients.

Cette configuration fonctionne paresseusement, c’est-à-dire que les sélections ne sont effectuées que lorsque getPrevious() ou getNext() sont appelées - et que n = sélectionne pour chaque appel.

La structure de la table:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

La classe:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}
8
acdcjunior

Dans les mappages XML Hibernate natifs, vous pouvez y parvenir en déclarant un mappage n à un avec l'attribut contraint défini sur vrai. Je ne suis pas sûr de l'équivalent de l'annotation Hibernate/JPA, et une recherche rapide dans le document ne fournit pas de réponse, mais j'espère que cela vous donnera une piste à suivre.

5
Rob H

Comme déjà parfaitement expliqué par ChssPly76, les mandataires d'Hibernate ne permettent pas les associations un-à-un sans contrainte (nullable), MAIS il y a une astuce expliquée ici pour éviter de mettre en place une instrumentation. L'idée est de tromper Hibernate que la classe d'entité que nous voulons utiliser a déjà été instrumentée: vous l'instruisez manuellement dans le code source. C'est facile! Je l'ai implémenté avec CGLib en tant que fournisseur de code intermédiaire et cela fonctionne (assurez-vous de configurer lazy = "no-proxy" et fetch = "select", pas "join" dans votre HBM).

Je pense que c’est une bonne alternative à l’instrumentation réelle réelle (je veux dire automatique) lorsque vous n’avez qu’une relation nullable un-à-un que vous souhaitez établir. paresseux. L'inconvénient principal est que la solution dépend du fournisseur de bytecode que vous utilisez. Par conséquent, commentez votre classe avec précision, car vous pourriez être amené à changer de fournisseur de bytecode ultérieurement; bien sûr, vous modifiez également votre modèle de haricot pour une raison technique, ce qui ne va pas.

3
Pino

Cette question est assez ancienne, mais avec Hibernate 5.1.10, il existe une nouvelle solution plus confortable.

Le chargement différé fonctionne à l'exception du côté parent d'une association @OneToOne. En effet, Hibernate n'a pas d'autre moyen de savoir s'il faut affecter un null ou un proxy à cette variable. Vous trouverez plus de détails dans cet article

  • Vous pouvez activer l'amélioration du bytecode avec chargement paresseux
  • Ou bien, vous pouvez simplement supprimer le côté parent et utiliser le côté client avec @MapsId comme expliqué dans l'article ci-dessus. De cette façon, vous constaterez que vous n’avez pas vraiment besoin du côté parent puisque l’enfant partage le même identifiant avec le parent, vous pouvez donc facilement le récupérer en connaissant l’identifiant du parent.
1
Toumi