这里有一些关于JPA实体的讨论,以及应该为JPA实体类使用哪些hashCode()/equals()实现。它们中的大多数(如果不是全部)依赖于Hibernate,但是我想中立地讨论它们的jpa实现(顺便说一下,我使用的是EclipseLink)。

所有可能的实现都有其自身的优点和缺点:

hashCode()/equals()契约一致性(不可变性)用于列表/集操作 是否可以检测到相同的对象(例如来自不同会话的对象,来自惰性加载数据结构的动态代理) 实体在分离(或非持久化)状态下是否正确运行

在我看来,有三种选择:

Do not override them; rely on Object.equals() and Object.hashCode() hashCode()/equals() work cannot identify identical objects, problems with dynamic proxies no problems with detached entities Override them, based on the primary key hashCode()/equals() are broken correct identity (for all managed entities) problems with detached entities Override them, based on the Business-Id (non-primary key fields; what about foreign keys?) hashCode()/equals() are broken correct identity (for all managed entities) no problems with detached entities

我的问题是:

我是否错过了一个选择和/或赞成/反对的观点? 你选择了什么,为什么?

更新1:

通过“hashCode()/equals()是坏的”,我的意思是连续的hashCode()调用可能返回不同的值,这(当正确实现时)在对象API文档的意义上不是坏的,但是当试图从Map、Set或其他基于哈希的集合中检索更改的实体时,会导致问题。因此,JPA实现(至少是EclipseLink)在某些情况下不能正确工作。

更新2:

谢谢你的回答——大部分问题都很有质量。 不幸的是,我仍然不确定哪种方法最适合实际应用程序,或者如何确定最适合我的应用程序的方法。所以,我将保持这个问题的开放性,希望有更多的讨论和/或意见。


当前回答

业务密钥方法不适合我们。我们使用DB生成的ID、临时临时tempId和重写equal()/hashcode()来解决这个困境。所有实体都是Entity的后代。优点:

DB中没有额外字段 在后代实体中没有额外的编码,一种方法适用于所有的实体 没有性能问题(如UUID), DB Id生成 使用hashmap没有问题(不需要记住equal & etc的使用)。 新实体的Hashcode即使在持久化后也不会及时更改

缺点:

序列化和反序列化非持久化实体可能会出现问题 从DB重新加载后,保存的实体的Hashcode可能会改变 非持久化对象被认为总是不同的(也许这是对的?) 还有什么?

看看我们的代码:

@MappedSuperclass
abstract public class Entity implements Serializable {

    @Id
    @GeneratedValue
    @Column(nullable = false, updatable = false)
    protected Long id;

    @Transient
    private Long tempId;

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    private void setTempId(Long tempId) {
        this.tempId = tempId;
    }

    // Fix Id on first call from equal() or hashCode()
    private Long getTempId() {
        if (tempId == null)
            // if we have id already, use it, else use 0
            setTempId(getId() == null ? 0 : getId());
        return tempId;
    }

    @Override
    public boolean equals(Object obj) {
        if (super.equals(obj))
            return true;
        // take proxied object into account
        if (obj == null || !Hibernate.getClass(obj).equals(this.getClass()))
            return false;
        Entity o = (Entity) obj;
        return getTempId() != 0 && o.getTempId() != 0 && getTempId().equals(o.getTempId());
    }

    // hash doesn't change in time
    @Override
    public int hashCode() {
        return getTempId() == 0 ? super.hashCode() : getTempId().hashCode();
    }
}

其他回答

这是每个使用Java和JPA的IT系统中的常见问题。痛点不仅仅是实现equals()和hashCode(),它还影响组织引用实体的方式以及其客户机引用同一实体的方式。我已经看够了没有商业钥匙的痛苦,以至于我写了自己的博客来表达我的观点。

简而言之:使用一个简短的、人类可读的、带有有意义前缀的顺序ID作为业务键,该ID生成时不依赖于RAM以外的任何存储。Twitter的雪花就是一个很好的例子。

显然,这里已经有了非常有用的答案,但我将告诉你我们是怎么做的。

我们什么也不做。

如果我们确实需要= /hashcode来处理集合,则使用uuid。 您只需在构造函数中创建UUID。我们使用http://wiki.fasterxml.com/JugHome作为UUID。UUID的CPU开销稍高,但与序列化和db访问相比便宜。

我使用类EntityBase和继承到我所有的JPA实体,这对我来说非常好。

/**
 * @author marcos.oliveira
 */
@MappedSuperclass
public abstract class EntityBase<TId extends Serializable> implements Serializable{
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected TId id;



    public TId getId() {
        return this.id;
    }

    public void setId(TId id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return (super.hashCode() * 907) + Objects.hashCode(getId());//this.getId().hashCode();
    }

    @Override
    public String toString() {
        return super.toString() + " [Id=" + id + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        EntityBase entity = (EntityBase) obj;
        if (entity.id == null || id == null) {
            return false;
        }
        return Objects.equals(id, entity.id);
    }
}

参考:https://thorben-janssen.com/ultimate-guide-to-implementing-equals-and-hashcode-with-hibernate/

我总是重写equals/hashcode,并基于业务id实现它。对我来说这是最合理的解决办法。请看下面的链接。

总而言之,这里列出了处理equals/hashCode的不同方法中哪些是有效的,哪些是无效的:

编辑:

为了解释为什么这对我有用:

I don't usually use hashed-based collection (HashMap/HashSet) in my JPA application. If I must, I prefer to create UniqueList solution. I think changing business id on runtime is not a best practice for any database application. On rare cases where there is no other solution, I'd do special treatment like remove the element and put it back to the hashed-based collection. For my model, I set the business id on constructor and doesn't provide setters for it. I let JPA implementation to change the field instead of the property. UUID solution seems to be overkill. Why UUID if you have natural business id? I would after all set the uniqueness of the business id in the database. Why having THREE indexes for each table in the database then?

我过去一直使用选项1,因为我知道这些讨论,并认为在我知道正确的事情之前最好什么都不做。这些系统仍在成功运行。

但是,下次我可能会尝试选项2 -使用数据库生成的Id。

如果未设置id, Hashcode和equals将抛出IllegalStateException。

这将防止涉及未保存实体的细微错误意外出现。

人们对这种方法有什么看法?