8
votes

Java Serialization Bug lorsqu'il est confronté à une dépendance circulaire avec un ensemble

Mon projet est un projet Java sur EJB3 à l'aide de Hibernate et WebLogic serveur.

Pour le bien de la commodité (et aussi loin que je comprenne, est typique de hibernate ), certaines des entités contiennent une dépendance circulaire (parent connaît l'enfant, l'enfant connaît le parent). En outre, pour certaines des classes enfant - le hashcode () et est égal () méthode dépend de leur parent (car il s'agit d'une clé unique). < P> Lorsque vous travaillez, j'ai vu un comportement étrange - certains des ensembles renvoyés du serveur au client, bien que contenant les bons éléments, agissaient comme ils ne contenaient aucun. Par exemple, un test simple tel que ceci: set.Contains (set.toarray () [0]) renvoyé false Bien que le code () La méthode est bonne.

Après un débogage étendu, j'ai pu produire 2 classes simples qui reproduisent le problème (je peux vous assurer que la fonction hashcode () dans les deux classes est réflexive, transitive et symétrique ): xxx

Ceci a produit la sortie suivante: xxx

Cela me dit que l'ordre Quel Java Serialise l'objet est faux! Il commence à sérialiser l'ensemble avant la chaîne et provoquant ainsi le problème ci-dessus.

Que dois-je faire dans cette situation? Existe-t-il une option (en dehors de la mise en œuvre ReadResolve pour de nombreuses entités ...) pour diriger Java pour sérialiser une classe dans un certain ordre? Aussi, est-ce fondamentalement faux pour une entité de base de son hashcode sur son parent?

EDIT : une solution a été suggérée par un collègue - parce que je «M Utilisation de Hibernate, chaque entité a un identifiant long unique. Je sais que Hibernate précise de ne pas utiliser cet identifiant dans la méthode des égaux - mais qu'en est-il de HashCode? L'utilisation de cet identifiant unique comme HASHCode semble résoudre le problème ci-dessus avec un risque minimal de problèmes de performance. Y a-t-il d'autres implications à utiliser l'identifiant comme hashcode?

deuxième modification : Je suis allé installé ma solution partielle (toutes les entités utilisent désormais le champ ID pour le hashcode ( ) Fonction et ne se relâche plus sur les autres complètes pour cela) mais, hélas, les bugs de sérialisation continuent de me défier! Vous trouverez ci-dessous un exemple de code avec un autre bug de sérialisation. Ce que je pense, c'est ce que cela se passe-t-il - CLASSA START DÉMARRERIALISER, voit qu'il a un classb à désérialiser et avant de désérialiser son identifiant, il commence à désérialiser le classb. B Démarrer pour désérialiser et voit qu'il a un ensemble de classa. L'instance de classe est partiellement désérialisée, mais même si Classb l'ajoute à l'ensemble (à l'aide de l'identifiant manquant de la classe), complète le désériializning, la classea complète ensuite et le bogue se produit.

Que puis-je faire pour résoudre cette?! Les dépendances circulaires sont une pratique très utilisée dans l'hibernate et je ne peux tout simplement pas accepter que je suis le seul avec ce problème.

Une autre solution possible est d'avoir une variable dédiée pour le hashcode (sera Calculé par l'ID de l'objet) et assurez-vous (voir ReadObject et WriteObject) qu'il sera lu avant tout autre objet. Qu'est-ce que tu penses? Y a-t-il des inconvénients à cette solution?

Le code d'échantillon: xxx


4 commentaires

Et si vous utilisez un linkedhashset ?


S'il vous plaît réparer les fautes de frappe. Le code ne compile pas.


La correction des fautes de frappe est assez difficile depuis que l'ordinateur sur lequel j'ai écrit le code n'est pas connecté à Internet ... Je ferai ce que je peux cependant faire.


J'ai corrigé les fautes de frappe et ajouté une seule chèque dans le code, j'ai également ajouté une ligne à la sortie d'origine ...


8 Réponses :


0
votes

C'est la méthode des égaux qui doivent être réflexifs, transitives et symétriques ...

La méthode HashCode doit avoir Ces propriétés :

Le contrat général de HASHCODE est:

Chaque fois que cela est appelé sur le même objet, plus d'une fois lors d'une exécution d'une application Java, la méthode de code HASHCode doit renvoyer systématiquement le même entier, à condition qu'aucune information utilisée dans des comparaisons égales sur l'objet est modifiée. Cet entier n'a pas besoin de rester compatible avec une exécution d'une application à une autre exécution de la même application.

Si deux objets sont égaux en fonction de la méthode Equals (Object), appelez la méthode HashCode sur chacun des deux objets doit produire le même résultat entier.

Il n'est pas nécessaire que si deux objets soient inégaux en fonction de la méthode égale (java.lang.Object), appelez la méthode de code HASHCode sur chacun des deux objets doit produire des résultats entiers distincts. Toutefois, le programmeur doit être conscient que la production de résultats entiers distincts pour des objets inégaux peut améliorer la performance des hashtables.

Ici, il ressemble au hashcode utilisé pour placer l'entrée dans l'ensemble lors de la désérialisation est différent de celui calculé pendant la contient (). BTW Comme vous avez remarqué que l'entrée est dans l'ensemble, vous ne pouvez tout simplement pas y accéder via HASHCode, si vous en boucle sur le contenu de l'ensemble, vous trouverez les éléments.

Solutions possibles:

  • avoir un hashcode ne s'appuie pas sur l'objet parent.
  • Utilisez une source de données qui n'utilise pas HASHCODE (liste, Treeet ...)
  • N'utilisez pas la méthode contient sur l'ensemble ...
  • Mettre en œuvre ReadResolvement pour recréer l'ensemble après la désiralisation ...

    [edit]: On dirait que vous n'êtes pas seul bug_id = 4957674


4 commentaires

Mon hashcode a ces propriétés (plus sa réflexive, etc.). Le problème ne se produit que pendant la sérilisation car MhashCodefield n'est pas sérilisé dans le bon ordre (après la sérialisation, le hashcode est bien).


En fait, le journal que vous écrivez dans la méthode ReadObject est écrit pendant la désérialisation, pas pendant la sérialisation ...


Oui, bien sûr, je me suis confus un peu, mais la signification est la même - le problème du hashcode se produit sur une heure très précise de l'application. Sinon, de tous les autres aspects, il est écrit très bien.


En ce qui concerne votre édition - il est bon de savoir que je ne suis pas seul :) Je n'ai pas vu de bon sanglotage là-bas, sauf (peut-être) un - modifiant la réveille de HASHMAP afin qu'il ne recompute pas le hashcode d'objets à Serializtion, à la place. Gardez les valeurs de code Hashcode avant, ce qui soulève la question - pourquoi HashMap recompe-t-il la carte à Desérialliztions?



0
votes

La déserarailisation lit les valeurs des deux champs ( mhashcodefield code> et msomeset code>) dans une matrice temporaire et après que les deux valeurs sont désérialisées, il définit les champs aux valeurs stockées.

Puisque hashset recalcule les codes de hachage de ses éléments lors de la désérialisation, il utilisera mhashascodefield code> quand il est toujours null. p>

solution possible est de marquer msomeset code> comme transitoire et écrire / le lire dans WriteObject / ReadObject. P>

@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
{
    System.out.println("Just started deserializing");
    in.defaultReadObject();
    mSomeSet=(Set<FieldOfSerializableClass>)in.readObject();
    System.out.println("Just finished deserializing");        
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
    System.out.println("Just started serializing");
    out.defaultWriteObject();
    out.writeObject(mSomeSet);
    System.out.println("Just finished serializing");        
}


1 commentaires

Marquage Mhashcodefield comme transitoire n'interférera pas avec Hibernate?



0
votes

En effet, Hibernate dit de ne pas utiliser l'identifiant comme hashcode, mais je crois qu'ils sont trop stricts à ce sujet. Cela n'a aucun sens que si l'identifiant est autogogené / auto-interprété par Hibernate. Dans ce cas, vous pourriez avoir un haricot qui reçoit sa valeur d'identification uniquement lorsque Hibernate décide de la persister à la base de données, donc dans cette situation, vous pourriez obtenir un comportement imprévisible à partir d'une méthode HASHCODE et / ou égale qui utilise l'ID. Toutefois, si l'identifiant est défini manuellement, c'est-à-dire que votre application concerne peupler cette valeur, je pense qu'il est parfaitement correct de l'utiliser dans vos méthodes HashCode / Equals. Est-ce le cas pour vous?


4 commentaires

Dans mon cas, l'identifiant est autogogéné et incrémenté par la DB que je crois. Maintenant, Hibernate interdit-il à l'aide d'ID dans Hashcode ou égale ? Ce que je veux, c'est d'utiliser l'ID pour le hashcode uniquement - les égaux fonctionneront toujours avec l'ID unique. Y aura-t-il un problème avec une telle configuration?


Ce n'est pas interdit en soi, hibernate juste "Frawns sur" ça. Article ICI: Community.jboss.org/wiki/equalsandhashcode , peut-être que cela vous aidera à décider.


J'ai lu l'article - il me semble que l'utilisation de l'identifiant n'est gênante que lorsqu'il est utilisé dans des égaux (). HashCode () est utilisé pour déterminer à quel godet une entrée sera insérée. Même si tous mes objets utilisaient '0' comme son HashCode, le pire qui se produira sera une dégradation des performances dans les hashtables, mais l'objet sera récupéré correctement tout de même. Suis-je trompé?


Oui, qui est théoriquement vrai, avec l'observation dûment nécessaire que si vous avez la mise en œuvre de votre hashcode, de telle sorte qu'il renvoie 0 pour toute instance, lors de l'utilisation d'une collection ressemblant à Hastable, vous obtiendrez la performance d'une liste liée.



0
votes

J'ajouterai une autre réponse car il est très différent de mon premier:

Voici une implémentation qui fonctionne sans champ transitoire, j'ai trouvé les informations nécessaires ici: SÉRIALISATION AVANCÉE ET ici .

D'ici comment j'ai également essayé d'utiliser l'attribut SerialPerSistTfields pour forcer que mhashcodefields est sérialisé en premier, mais il n'a pas fait Aide ... xxx


3 commentaires

Existe-t-il un moyen de générer cette solution? Ce sera trop complexe et prendra du temps pour la mettre en œuvre à toutes les entités problématiques.


Utiliser une liste au lieu d'un ensemble n'est pas une option?


Je l'ai considéré, et il peut être applicable à la plupart des cas, mais HashMap (et non défini) est essentiel dans certaines des entités.



1
votes

Ainsi, comme je l'ai lu, vous basez le hashcode de FielfSerializAbeclass code> sur l'objet parent. Cela semble être la cause ultime de votre problème et une conception très discutable. hashcode () code> et équivaut à () code> Méthode traite avec l'identité d'objet et ne doit pas le tout être lié à ce que les parent les contiennent. L'idée que l'identité d'un objet change en fonction de quel objet parent possède-t-elle est très étranger pour moi au moins et est la raison ultime pour laquelle votre code ne fonctionne pas.

Bien que les autres réponses aient quelques façons de travailler autour du Problème, je pense que le moyen le plus simple de résoudre ce problème est de donner le FieldOfSerializAbeclass code> classe sa propre identité. Vous pouvez copier le mhashcodefield code> à partir du Serializableclass code> dans le champ FielfSerializAbeclass code>. Lorsque le parent est défini sur l'objet, vous pouvez prendre son mhashascodefield code> et le stocker localement. P>

@Override
public int hashCode() {
    return ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
}


3 commentaires

Je suis conscient que les objets de couplage ne sont pas une bonne pratique (à dire le moins!) Mais la situation ci-dessus est celle qui représente correctement la DB. Considérez un cours et un professeur. Le même parcours (par exemple "Java avancé") peut être enseigné simultanément pendant le semestre avec différents enseignants. L'enseignant est une clé vitale du cours (par exemple, clé de l'enseignant et nom du cours). Bien sûr, je peux affecter le cours un identifiant unique (et je le fais), mais comme mentionné précédemment, ce n'est pas une bonne pratique en hibernate.


Le professeur peut être «vital» au cours mais cela ne devrait pas avoir une incidence sur l'identité du cours. Le professeur Smith peut enseigner le cours Java avancé du vendredi que les gens s'inscrivent. S'il passe à celui mardi, les personnes qui se sont inscrites ne sont pas immédiatement déplacées à mardi. Que le professeur change ne devrait pas affecter la classe. Mais vraiment, c'est une métaphore et de ce dont nous parlons est des modèles de conception Java. Les identités doivent pas être affectées par l'objet de l'objet.


En termes d'hibernation, sans vouloir utiliser l'identité en tant que code HashCode, cela ne s'applique que si l'objet n'a pas été inséré dans la base de données. Vous créez le cours dans la base de données avant de pouvoir l'ajouter à la collection du professeur. Je ne vois pas de problème. Docs. jboss.org/hibernate/stable/core/reference/en-us/html/...



0
votes

me semble que ceci est un bug en Java, pas votre code source. Bien que les réponses ci-dessus donnent de bonnes options de contournement, la meilleure solution serait pour Java pour corriger comment la désérialisation fonctionne pour tenir compte des références circulaires et des ensembles / hachements.

Voir ici pour faire un nouveau rapport de bogue: http://bugrepor.sun.com/bugreport/

Plus les gens qui signalent ce bogue, plus ils le répareront. Moi aussi, je reçois cette erreur dans mon projet et les travaux sont beaucoup plus efforts que ce qu'ils ne mènent.

Aussi, voici un rapport de bogue similaire que j'ai trouvé: http://bugs.sun.com/view_bug.do ;jsessionid=fb27da16BB769FFFFFFFFEBCE29D31B2574E ? bug_id = 6208166


1 commentaires

Je ne pense pas qu'ils le répareront jamais - en fait, Josh Bloch a donné ce bug en tant que casse-tête dans son livre Java Puzzels (P91) où il a déclaré que cela ne sera probablement jamais corrigé.



0
votes

Je suis tombé sur le même problème. Je pense que vous avez raison dans votre deuxième édition sur la cause. Voici ma réplication la plus simple du problème: xxx

sortie: xxx

Ce test échoue. La solution la plus simple que j'ai trouvée était de conserver une copie du hashcode. Parce que c'est une primaire, il est défini lorsque l'objet est initialisé pendant la désérialisation, pas plus tard: xxx

Le test passe maintenant.


0 commentaires

0
votes

JDK-4957674: (Coll) Entrées de hachage placées dans des godets incorrects lors de la désérialisation

Lors de la désérialisation d'un hashmap, la méthode ReadObject () lit les paires de la valeur de clé et repasse la carte en appelant HashCode () sur les touches.

mais si la mise en œuvre des touches de HASHCode () dépend de la variable interne de la clé, et si cette variable n'a pas encore été désérialisée à ce moment-là, HashCode () donnera le mauvais résultat, envoyant la clé dans le faux seau de hachage.

Solution de contournement: xxx


0 commentaires