我的项目是 EJB3 上的 java 项目,使用 Hibernate 和 Weblogic 服务器。
为了方便起见(据我所知,hibernate 很典型),一些实体包含循环依赖(父知道子,子知道父)。此外,对于某些子类 - hashCode() 和 equals() 方法取决于它们的父类(因为它是唯一键)。
在工作时,我看到了一个奇怪的行为 - 从服务器返回到客户端的一些 Set,虽然包含正确的元素,但表现得好像它们什么都不包含一样。例如,一个像这样的简单测试:set.contains(set.toArray()[0]) 虽然 hashCode() 返回了 false > 方法不错。
经过大量调试后,我能够生成 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
This tells me that the order in which Java serializes the object is wrong! It starts serializing the Set before the String, and thus causing the above problem.
What should I do in this situation? Is there any option (aside from implementing readResolve for many entities...) to direct java to serialize a class in a certain order?
Also, is it fundamentally wrong for an entity to base its hashCode on its parent?
Edit: A solution was suggested by a colleague - Because I'm using Hibernate, every entity has a unique long ID. I know that Hibernate specify not to use this ID in the equals method - but what about hashCode? Using this unique ID as hashcode seems to solve the above problem with a minimal risk of performance issues. Are there any other implications to using the ID as hashcode?
SECOND EDIT: I went and implemented my partial solution (All of the enteties now use the ID field for the hashCode() function and no longer relay on other enteties for it) but, alas, Serialization bugs still continue to plague me! Below is a sample code with another serialization bug. What I think is happening is this - ClassA start deserializing, sees it has a ClassB to deserialize and BEFORE it deserializes its ID, it start deserializing the ClassB. B start to deserialize and Sees it has a Set of ClassA. The ClassA instance is partialy deserialized, but even though ClassB adds it to the Set (using the missing ID of ClassA), completes the deserializning, ClassA then completes and the bug occurs.
What can I do to solve this?! Circular dependencies is a very used practice in Hibernate and I just can't accept it that i'm the only one with this problem.
Another possible solution is to have a dedicated variable for the hashCode (will be calculated by the object's ID) and make sure (view readObject and writeObject) that it will be read BEFORE VERY OTHER OBJECT. What do you think? Are there any disadvantages to this solution?
The sample code:
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;
}
}
}
最佳答案
因此,在我阅读它时,您将 FieldOfSerializableClass 的 hashCode 基于父对象。这似乎是您问题的最终原因,也是一个非常值得怀疑的设计。 hashCode() 和 equals() 方法处理对象身份,根本不应该与包含它们的父对象相关。对象的身份根据拥有它的父对象而改变的想法至少对我来说是非常陌生的,这是您的代码不起作用的最终原因。
虽然其他答案有一些解决问题的方法,但我认为解决此问题的最简单方法是为 FieldOfSerializableClass 类提供自己的标识。您可以将 mHashCodeField 从 SerializableClass 复制到 FieldOfSerializableClass。在对象上设置父级后,您可以获取其 mHashCodeField 并将其存储在本地。
public void setParentLink(SerializableClass pParentLink) {
this.mHashCodeField = pParentLink.mHashCodeField;
mParentLink = pParentLink;
}
然后 hashcode(和 equals)方法看起来类似于 SerializableClass 的方法。
@Override
public int hashCode() {
return ((mHashCodeField == null) ? 0 : mHashCodeField.hashCode());
}
但实际上您应该考虑更改代码,以降低亲子关系的耦合度。考虑一下如果您在一个字段上调用 setParentLink() 而它已经在另一个 SerializableClass 集合中会发生什么。突然之间,原始类甚至无法在其集合中找到该项目,因为它的身份已更改。就 Java 对象而言,为 FieldOfSerializableClass 类分配一些与父类不同的排序标识是此处的最佳模式。
如果您不能使用 FieldOfSerializableClass 作为正确的标识。但我会使用 Hibernate 提供给您的自动生成的 ID。您只需要确保该对象在被放入另一个对象的集合之前已被插入到数据库中。
关于面对 Set 的循环依赖时的 Java 序列化错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7901006/
我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He
我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c
我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file