Ошибка сериализации Java при столкновении с циклической зависимостью с Set

Мой проект - это java проект поверх EJB3 с использованием Hibernate и сервер Weblogic .

Для удобства (и, насколько я понимаю, это типично для hibernate ), некоторые объекты содержат циклическую зависимость (родитель знает ребенка, дочерний элемент знает родителя). Кроме того, для некоторых дочерних классов методы hashCode () и equals () зависят от их родителя (поскольку это уникальный ключ).

Во время работы я заметил странное поведение - некоторые из наборов, которые возвращались от сервера к клиенту, хотя и содержали правильные элементы, действовали так, как будто они не содержали ни одного. Например, простой тест, такой как этот: set.contains (set.toArray () [0]) вернул false , хотя метод hashCode () является хороший.

После обширной отладки мне удалось создать 2 простых класса, воспроизводящих проблему (могу вас заверить, что функция hashCode () в обоих классах является рефлексивной, транзитивной и симметричной ) :

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class ClientTest implements Serializable {
    public static void main(String[] args) throws Exception {
        SerializableClass serializationTest = new SerializableClass();
        FieldOfSerializableClass hashMember = new FieldOfSerializableClass();
        hashMember.setParentLink(serializationTest);
        serializationTest.setHashCodeField("Some string");
        serializationTest
                .setSomeSet(new HashSet<FieldOfSerializableClass>());
        serializationTest.getSomeSet().add(hashMember);
        System.out.println("Does it contain its member? (should return true!) "
                + serializationTest.getSomeSet().contains(hashMember));
        new ObjectOutputStream(new FileOutputStream("temp"))
                .writeObject(serializationTest);
        SerializableClass testAfterDeserialize = (SerializableClass) new ObjectInputStream(
                new FileInputStream(new File("temp"))).readObject();
        System.out.println("Does it contain its member? (should return true!) "
                + testAfterDeserialize.getSomeSet().contains(hashMember));

        for (Object o : testAfterDeserialize.getSomeSet()) {
            System.out.println("Does it contain its member by equality? (should return true!) "+ o.equals(hashMember));
        }

    }

    public static class SerializableClass implements Serializable {
        private Set<FieldOfSerializableClass> mSomeSet;
        private String mHashCodeField;

        public void setSomeSet(Set<FieldOfSerializableClass> pSomeSet) {
            mSomeSet = pSomeSet;
        }

        public Set<FieldOfSerializableClass> getSomeSet() {
            return mSomeSet;
        }

        public void setHashCodeField(String pHashCodeField) {
            mHashCodeField = pHashCodeField;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;

            System.out.println("In hashCode - value of mHashCodeField: "
                    + mHashCodeField);
            result = prime
                    * result
                    + ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SerializableClass other = (SerializableClass) obj;

            if (mHashCodeField == null) {
                if (other.mHashCodeField != null) {
                    return false;
                }
            } else if (!mHashCodeField.equals(other.mHashCodeField))
                return false;
            return true;
        }

        private void readObject(java.io.ObjectInputStream in)
                throws IOException, ClassNotFoundException {
            System.out.println("Just started serializing");
            in.defaultReadObject();
            System.out.println("Just finished serializing");
        }
    }

    public static class FieldOfSerializableClass implements Serializable {
        private SerializableClass mParentLink;

        public void setParentLink(SerializableClass pParentLink) {
            mParentLink = pParentLink;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((mParentLink == null) ? 0 : mParentLink.hashCode());

            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            FieldOfSerializableClass other = (FieldOfSerializableClass) obj;
            if (mParentLink == null) {
                if (other.mParentLink != null) {
                    return false;
                }
            } else if (!mParentLink.equals(other.mParentLink))
                return false;
            return true;
        }
    }

}

Это привело к следующему выводу:

    In hashCode - value of mHashCodeField: Some string
    In hashCode - value of mHashCodeField: Some string
    Does it contain its member? (should return true!) true
    Just started serializing
    In hashCode - value of mHashCodeField: null
    Just finished serializing
    In hashCode - value of mHashCodeField: Some string
    Does it contain its member? (should return true!) false
    Does it contain its member by equality? (should return true!) true

Это говорит мне, что порядок, в котором Java сериализует объект, неправильный! Он начинает сериализацию Set до String, что вызывает указанную выше проблему.

Что мне делать в этой ситуации? Есть ли какой-либо вариант (помимо реализации readResolve для многих сущностей ...), чтобы указать Java для сериализации класса в определенном порядке? Кроме того, фундаментально неправильно для сущности базировать его hashCode его родительского элемента?

Edit : решение было предложено коллегой - поскольку я использую Hibernate, каждая сущность имеет уникальный длинный идентификатор. Я знаю, что Hibernate указывает не использовать этот идентификатор в методе equals, но как насчет hashCode? Использование этого уникального идентификатора в качестве хэш-кода, похоже, решает указанную выше проблему с минимальным риском проблем с производительностью. Есть ли какие-либо другие последствия использования идентификатора в качестве хэш-кода?

ВТОРОЙ РЕДАКТИРОВАНИЕ : Я пошел и реализовал свое частичное решение (все объекты теперь используют поле идентификатора для функции hashCode () и больше не передают другие enteties для этого) но, увы, ошибки сериализации все еще продолжают меня преследовать! Ниже приведен пример кода с другой ошибкой сериализации. Я думаю, что происходит следующее: ClassA начинает десериализацию, видит, что у него есть ClassB для десериализации, и ДО того, как он десериализует свой идентификатор, он начинает десериализацию ClassB. B начинает десериализацию и видит, что у него есть набор ClassA. Экземпляр ClassA частично десериализован, но даже несмотря на то, что ClassB добавляет его в Set (используя отсутствующий идентификатор ClassA), десериализация завершается, ClassA затем завершается, и возникает ошибка.

Что я могу сделать, чтобы решить эту проблему ?! Круговые зависимости - очень распространенная практика в Hibernate, и я просто не могу согласиться с тем, что я единственный, у кого эта проблема.

Другое возможное решение - создать специальную переменную для hashCode (будет вычисляться по идентификатору объекта) и убедиться (просмотрите readObject и writeObject), что она будет прочитана ПЕРЕД ОЧЕНЬ ДРУГИМ ОБЪЕКТОМ. Что вы думаете? Есть ли недостатки у этого решения?

Пример кода:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class Test implements Serializable
{
    public static void main(String[] args) throws Exception
    {
        ClassA aClass = new ClassA();
        aClass.setId(Long.valueOf(321));

        ClassB bClass = new ClassB();
        bClass.setId(Long.valueOf(921));

        Set<ClassA> set = new HashSet<ClassA>();
        set.add(aClass);

        bClass.setSetfield(set);
        aClass.setBField(bClass);

        Set<ClassA> goodClassA = aClass.getBField().getSetfield();
        Set<ClassA> badClassA = serializeAndDeserialize(aClass).getBField().getSetfield();

        System.out.println("Does it contain its member? (should return true!) " + goodClassA.contains(goodClassA.toArray()[0]));
        System.out.println("Does it contain its member? (should return true!) " + badClassA.contains(badClassA.toArray()[0]));
    }

    public static ClassA serializeAndDeserialize(ClassA s) throws Exception
    {
        new ObjectOutputStream(new FileOutputStream(new File("temp"))).writeObject(s);
        return (ClassA) new ObjectInputStream(new FileInputStream(new File("temp"))).readObject();
    }

    public static class ClassB implements Serializable
    {
        private Long mId;
        private Set<ClassA> mSetfield = new HashSet<ClassA>();
        public Long getmId() {
            return mId;
        }
        public void setId(Long mId) {
            this.mId = mId;
        }
        public Set<ClassA> getSetfield() {
            return mSetfield;
        }
        public void setSetfield(Set<ClassA> mSetfield) {
            this.mSetfield = mSetfield;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((mId == null) ? 0 : mId.hashCode());
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ClassB other = (ClassB) obj;
            if (mId == null) {
                if (other.mId != null)
                    return false;
            } else if (!mId.equals(other.mId))
                return false;
            return true;
        }       
    }

    public static class ClassA implements Serializable
    {
        private Long mId;
        private ClassB mBField;
        public Long getmId() {
            return mId;
        }
        public void setId(Long mId) {
            this.mId = mId;
        }
        public ClassB getBField() {
            return mBField;
        }
        public void setBField(ClassB mBField) {
            this.mBField = mBField;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((mId == null) ? 0 : mId.hashCode());
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ClassA other = (ClassA) obj;
            if (mId == null) {
                if (other.mId != null)
                    return false;
            } else if (!mId.equals(other.mId))
                return false;
            return true;
        }
    }
}
7
задан Ran 30 October 2011 в 14:50
поделиться