Java序列化错误,当遇到具有集合的循环依赖项时
我的项目是一个Java序列化错误,当遇到具有集合的循环依赖项时,java,hibernate,serialization,Java,Hibernate,Serialization,我的项目是一个java项目,在EJB3上使用Hibernate和Weblogic服务器 为了方便起见(据我所知,这是典型的hibernate),一些实体包含循环依赖项(父知道子,子知道父)。此外,对于某些子类,hashCode()和equals()方法依赖于它们的父类(因为它是唯一的键) 在工作时,我看到了一种奇怪的行为——一些从服务器返回到客户机的集合,虽然包含正确的元素,但它们的行为却好像不包含任何元素。例如,这样一个简单的测试:set.contains(set.toArray()[0])返
java
项目,在EJB3
上使用Hibernate和Weblogic
服务器
为了方便起见(据我所知,这是典型的hibernate
),一些实体包含循环依赖项(父知道子,子知道父)。此外,对于某些子类,hashCode()
和equals()
方法依赖于它们的父类(因为它是唯一的键)
在工作时,我看到了一种奇怪的行为——一些从服务器返回到客户机的集合,虽然包含正确的元素,但它们的行为却好像不包含任何元素。例如,这样一个简单的测试:set.contains(set.toArray()[0])
返回了false
,尽管hashCode()
方法是一个好方法
经过大量调试后,我能够生成两个简单的类来重现问题(我可以向您保证两个类中的hashCode()
函数都是自反、传递和对称的):
equals方法必须是自反的、传递的和对称的 hashCode方法必须具有: hashCode的总合同为: 在Java应用程序的执行过程中,每当在同一对象上多次调用hashCode方法时,只要没有修改对象上的equals比较中使用的信息,hashCode方法必须始终返回相同的整数。从应用程序的一次执行到同一应用程序的另一次执行,该整数不必保持一致 如果根据equals(Object)方法两个对象相等,那么对两个对象中的每一个调用hashCode方法必须产生相同的整数结果 根据equals(java.lang.Object)方法,如果两个对象不相等,则对这两个对象中的每一个调用hashCode方法都必须产生不同的整数结果,这不是必需的。但是,程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能 在这里,在反序列化期间用于将条目放入集合中的哈希代码似乎与在contains()期间计算的哈希代码不同。顺便说一句,您注意到条目在集合中,您无法通过其哈希代码访问它,如果您循环集合的内容,您将找到元素 可能的解决办法:
- 具有不依赖父对象的哈希代码
- 使用不使用哈希代码的数据结构(列表、树集…)
- 不要对集合使用contains方法
- 实现ReadResolve以在理想化后重新创建集合
[编辑]:似乎不止您一人反序列化将两个字段(
mHashCodeField
和mSomeSet
)的值读取到临时数组中,并在两个值反序列化后将字段设置为存储值
由于HashSet在反序列化期间重新计算其元素的哈希代码,因此当它仍然为空时,它将使用mHashCodeField
可能的解决方案是将mSomeSet
标记为瞬态,并在writeObject/readObject中写入/读取它
@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");
}
@SuppressWarnings(“未选中”)
私有void readObject(java.io.ObjectInputStream in)引发IOException、ClassNotFoundException
{
System.out.println(“刚刚开始反序列化”);
in.defaultReadObject();
mSomeSet=(Set)in.readObject();
System.out.println(“刚刚完成反序列化”);
}
私有void writeObject(java.io.ObjectOutputStream out)抛出IOException
{
System.out.println(“刚刚开始序列化”);
out.defaultWriteObject();
out.writeObject(mSomeSet);
System.out.println(“刚刚完成序列化”);
}
事实上,Hibernate说不要将id用作哈希代码,但我认为他们对它太严格了。只有当id由Hibernate自动生成/自动递增时,这才有意义。在这种情况下,您可能有一个bean,它只有在Hibernate决定实际将其持久化到数据库时才接收其id值,因此在这种情况下,您可能会从使用id的hashcode和/或equals方法中获得不可预测的行为。但是,如果手动设置id,也就是说,您的应用程序处理填充这个值,那么我相信在hashcode/equals方法中使用它是完全可以的。你就是这样吗 我添加了另一个答案,因为它与我的第一个答案非常不同:
这是一个没有瞬态场的实现,我在这里找到了必要的信息:和
顺便说一下,我还尝试使用serialPersistentFields
属性强制先序列化mHashCodeFields,但没有帮助
public static class SerializableClass implements Serializable {
// this tells the serialization mechanism to serialize only mHasCodeField...
private final static ObjectStreamField[]
serialPersistentFields = {
new ObjectStreamField(
"mHashCodeField", String.class)
};
private String mHashCodeField;
private Set<FieldOfSerializableClass> mSomeSet;
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 writeObject(java.io.ObjectOutputStream out)
throws IOException, ClassNotFoundException {
System.out.println("Just started serializing");
out.defaultWriteObject();
out.writeObject(mSomeSet);
System.out.println("In writeObject - value of mHashCodeField: "
+ mHashCodeField);
System.out.println("Just finished serializing");
}
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("In readObject - value of mHashCodeField: "
+ mHashCodeField);
System.out.println("Just finished deserializing");
}
}
公共静态类SerializableClass实现可序列化{
//这告诉序列化机制只序列化mHasCodeField。。。
私有最终静态ObjectStreamField[]
serialPersistentFields={
新ObjectStreamField(
“mHashCodeField”,String.class)
};
私有字符串字段;
私有集mSomeSet;
公共无效集合集合(集合pSomeSet){
mSomeSet=pSomeSet;
}
公共集getSomeSet(){
返回mSomeSet;
}
public void setHashCodeField(String pHashCodeField){
mHashCodeField=相码字段;
}
@凌驾
公共int hashCode(){
最终整数素数=31;
int结果=1;
System.out.println(“在hashCode中-mHashCodeField的值:”
+mHashCodeField);
结果=素数
*结果
+((mHashCodeField==null)?0:mHashCodeField.hashCode());
返回结果;
}
@凌驾
公共布尔等于(对象obj){
if(this==obj)
返回true;
如果
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;
}
}
}
@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");
}
public static class SerializableClass implements Serializable {
// this tells the serialization mechanism to serialize only mHasCodeField...
private final static ObjectStreamField[]
serialPersistentFields = {
new ObjectStreamField(
"mHashCodeField", String.class)
};
private String mHashCodeField;
private Set<FieldOfSerializableClass> mSomeSet;
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 writeObject(java.io.ObjectOutputStream out)
throws IOException, ClassNotFoundException {
System.out.println("Just started serializing");
out.defaultWriteObject();
out.writeObject(mSomeSet);
System.out.println("In writeObject - value of mHashCodeField: "
+ mHashCodeField);
System.out.println("Just finished serializing");
}
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("In readObject - value of mHashCodeField: "
+ mHashCodeField);
System.out.println("Just finished deserializing");
}
}
public void setParentLink(SerializableClass pParentLink) {
this.mHashCodeField = pParentLink.mHashCodeField;
mParentLink = pParentLink;
}
@Override
public int hashCode() {
return ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
}
public class Test {
static class Thing implements Serializable {
String name;
Set<Thing> others = new HashSet<Thing>();
@Override
public int hashCode() {
if (name == null) {
System.out.println("hashcode called with null name!");
}
return name == null ? 0 : name.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof Thing && ((Thing) o).name == name;
}
}
@org.junit.Test
public void testHashSetCircularDependencySerialization() throws Exception {
Thing thing = new Thing();
thing.name = "thing";
Thing thing2 = new Thing();
thing2.name = "thing2";
thing.others.add(thing2);
thing2.others.add(thing);
assertTrue(thing2.others.contains(thing));
Thing thingCopy = (Thing) serializeAndDeserialize(thing);
Thing thing2Copy = thingCopy.others.iterator().next();
assertTrue(thing2Copy.others.contains(thingCopy));
}
public static Object serializeAndDeserialize(Object other) throws Exception {
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
new ObjectOutputStream(byteOutputStream).writeObject(other);
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteOutputStream.toByteArray());
return new ObjectInputStream(byteInputStream).readObject();
}
}
hashcode called with null name!
int hashcode;
@Override
public int hashCode() {
if (hashcode != 0) {
return hashcode;
}
hashcode = name == null ? 0 : name.hashCode();
return hashcode;
}