对于具有数据库管理ID的persistance对象,实现equals()(和hashCode(),我将仅讨论equals())存在众所周知的问题。 新对象未存储在数据库中,因此没有数据库标识,因此其“id”字段为空(如果是基本类型,则为0)。
如果equals查看id,它会认为所有新对象都是相等的,一旦它获得id,哈希代码就会改变,所以如果它已经在哈希敏感集合中,它将无法找到。
一种解决方案是使用业务密钥,但有时除了代理项ID之外的所有内容都是可变的。 另一种解决方案是在创建对象时生成(还有一个,或者也将其用作数据库id)代理id。
方法我没有看到提到的是,当id为null时,在equals中使用id并使equals(和hashCode())失败(抛出IllegalStateException)。 (并记录此行为) 这样它仍然不能在哈希集合中,但它不能被意外地放在那里。并且为了在没有id的情况下将它放入集合中,可以使用一些包装器。 好/坏主意?它有遗留问题吗?
正如kan所指出的,如果子对象应该放在Set属性中并且与它们的父对象持久存在,那么在它们被持久化之前无法将对象放入Set中是一个大问题(并且TreeSet没有帮助,因为它使用equals(),即使它不使用hashCode())。 我主要使用list实现子实体,所以它不需要显示,但肯定是问题。
答案 0 :(得分:1)
我总是使用自动生成的ID并且从未遇到过问题。您可以在实例化时强制实施对象,也可以使用服务层/工厂持久保存。
我认为任何其他字段(组成商业密钥)更改的可能性比在散列映射中使用非持久对象然后在同时持续导致查找的可能性更大失败。
这个问题,imho,有点over-analysed。自动生成的id通常是我想要做的唯一测试,在很多情况下没有别的意义。我采取的方法是,如果正在使用/比较非持久化对象,则问题出在业务逻辑中,而不是基础的equals / hashcode方法
要专门回答非法状态异常的想法,当对象不相等和/或没有持久化时抛出异常似乎相当戏剧化。
答案 1 :(得分:-1)
我使用下一个代码。它涵盖了大多数情况,它可以用于我认为在使用ORM时可能发生的所有情况。
public class VersionedEntity
{
private static final long serialVersionUID=1L;
private Long id;
private long version;
@Transient
private int hashCode;
...
public void setId(final Long id)
{
if(this.id != null && !this.id.equals(id))
throw new IllegalArgumentException(this+" has an ID already, cannot change it to "+id);
this.id = id;
}
@Override
public String toString() {
return getClass().getName()+'#'+getId();
}
public boolean equals(final Object o)
{
if (this==o) return true;
if (!(o instanceof VersionedEntity))
return false;
final VersionedEntity entity=(VersionedEntity) o;
final Long id1 = entity.getId();
final Long id2 = getId();
if(id1==null && id2==null)
return super.equals(o);
return id1 != null
&& id2 != null
&& id2.equals(id1);
}
public int hashCode()
{
if(hashCode == 0)
{
hashCode = id != null ? id.hashCode() : super.hashCode();
if(hashCode == 0)
hashCode = 42;
}
return hashCode;
}
}
您必须能够在分配ID之前使用集合。如果要创建具有Set
属性且包含其他对象包的对象,则必须将它们作为元素添加到Set
。