这里有一些关于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:

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


当前回答

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

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

其他回答

我个人已经在不同的项目中使用了这三种策略。我必须说,选项1在我看来是现实应用中最可行的。以我的经验来看,打破hashCode()/equals()一致性会导致许多疯狂的错误,因为你每次都会遇到这样的情况:在一个实体被添加到一个集合后,相等的结果发生了变化。

但也有更多的选择(也有它们的优点和缺点):


a) hashCode/equals基于一组不可变的、非空的、构造函数赋值的字段

(+)三个标准都有保证

(-)字段值必须可用以创建新实例

(-)如果你必须改变其中一个,处理起来会很复杂


b) hashCode/equals基于应用程序(在构造函数中)分配的主键,而不是JPA

(+)三个标准都有保证

(-)您不能利用简单可靠的ID生成策略,如DB序列

(-)如果在分布式环境(客户端/服务器)或应用服务器集群中创建新实体会很复杂


c) hashCode/equals基于实体的构造函数分配的UUID

(+)三个标准都有保证

(-)生成UUID的开销

(-)可能会有使用两次相同UUID的风险,这取决于所使用的算法(可能由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/

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

我们什么也不做。

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

如果UUID是许多人的答案,为什么我们不使用业务层的工厂方法来创建实体并在创建时分配主键呢?

例如:

@ManagedBean
public class MyCarFacade {
  public Car createCar(){
    Car car = new Car();
    em.persist(car);
    return car;
  }
}

通过这种方式,我们可以从持久化提供程序获得实体的默认主键,并且我们的hashCode()和equals()函数可以依赖于它。

我们还可以声明Car的构造函数受保护,然后在业务方法中使用反射来访问它们。这样,开发人员就不会打算用new实例化Car,而是通过factory方法。

来说,如何?

我总是重写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?