没有自然键可用时实现equals()和hashCode()?

时间:2011-07-08 23:06:40

标签: java jpa equals identity hashcode

这个问题基本上是问题的后续问题:

Should I write equals() methods in JPA entities?What is the best practice when implementing equals() for entities with generated ids

有些背景......

您可以定期遇到以下主要核心星座:

  1. 自然键(业务键):通常是实体的一组真实的多列​​属性
  2. 人工键(代理键):无意义,通常是自动递增(IDENTITY,AUTO_INCREMENT,AUTOINCREMENT,SEQUENCE,SERIAL,...)ID
  3. 混合密钥(半自然/半仿真密钥):通常由一个人工ID和一些额外的自然列组成,例如任何引用另一个使用ID并扩展该密钥的表的表(entity_id,ordinal_nbr) )或类似的。
  4. 频繁场景:对根,分支或叶继承表的多对一引用,它们通过识别关系/从属密钥共享一个共同的“愚蠢”ID。 当另一个表需要引用所有实体类型时,根(和分支)表通常是有意义的,例如, PostAddresses - >联系人,其中联系人有子表人员,俱乐部, 和设施,没有任何共同点,但是“可联系”。

    现在到JPA:

    在Java中,我们可以创建新的实体对象,其PK可能不完整(null或部分为null),这是DBMS最终阻止我们插入数据库的实体(行)。

    但是,在使用应用程序代码时,即使新实体对象尚未具有PK值,也可以将新的(或分离的)实体与现有(托管)实体进行比较,这通常很方便。要为具有自然键列的任何实体实现此目的,请使用它们来实现equals()和hashCode()(如其他两个SO帖子所示)。

    问题:

    但是,当没有自然/业务密钥可以确定时,你会怎么做,就像Contacts表的情况一样,它基本上只是一个ID(加上一个鉴别器)?基于equals()和hashCode()实现的列选择策略是什么? (上面的人工键2.和3.)

    显然没有多少选择......

    一个(天真的)目标是实现相同的“瞬态可比性”。可以吗?如果没有,那么人工ID equals()和hashCode()实现的一般方法是什么样的?


    注意:我已经在使用Apache EqualsBuilder和HashCodeBuilder ......我故意“使我的问题”变得“天真化”。

3 个答案:

答案 0 :(得分:3)

我认为这个主题比讨论更简单。

获取数据库ID(如果存在),否则使用Object#equals / object identity

为什么呢?如果将新实体放入数据库,JPA除了将新生成的id从数据库映射到实体对象标识之外别无其他。另一方面,这意味着对象标识也是事先的主键。

讨论的重点往往是假设,即具有相同属性的两个业务对象是相等的。但他们不是。 例如。如果您不想复制地址值,则具有相同街道和城市的两个地址仅相等。但是,然后你将它们变成数据库中的主键,这导致你总是为业务对象获得主键。 如果允许业务对象使用重复的地址,则对象标识是主键,因为它是两个地址之间的唯一区别。

在持久化实体之后,数据库ID确实完全占用了作业,因为您现在可以拥有仅共享相同数据库ID的同一实体的克隆。 (但现在可以有几个内存位置/对象标识)

答案 1 :(得分:1)

如果你在对象上找不到一组属性来区别于同类的其他对象那么你就无法比较这些对象了吗?如果您提供详细的用例,可能会有更多用例,但如果与id和鉴别器联系,在没有id的情况下,您只能比较具有相同鉴别器的对象组。如果保证组只有一个元素,那么鉴别器就是你的关键。

答案 2 :(得分:1)

一种常用的技术是将UUID用于标识符,这有一些缺点。

他们制作丑陋的网址,据说基于这样一个长标识符查询实体会产生性能影响。长UUID也会导致数据库索引变得太大。

UUID的优点是您不必为每个实体实现单独的hashCode()equals()方法。

我决定在我自己的项目中使用的解决方案是混合传统的分配标识符,并在内部使用UUID用于hashCode()equals()方法。它看起来像这样:

@Configurable
@MappedSuperclass
@EntityListeners({ModelListener.class})
@SuppressWarnings("serial")
public abstract class ModelBase implements Serializable {

     //~~ Instance Fields =====================================

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

    @Column(name="__UUID__", unique=true, nullable=false, updatable=false, length = 36)
    private String uuid = java.util.UUID.randomUUID().toString();

    //~ Business Methods =====================================

    @Override
    public String toString() {
        return new ToStringCreator(this)
            .append("id", getId())
            .append("uuid", uuid())
            .append("version", getVersion())
             .toString(); 
    }

    @Override
    public int hashCode() {
        return uuid().hashCode();
    }

    @Override
    public boolean equals(Object o) {
        return (o == this || (o instanceof ModelBase && uuid().equals(((ModelBase)o).uuid())));
     }

    /**
     * Returns this objects UUID.
     * 
     * @return - This object's UUID.
     */
    public String uuid() {
        return uuid;
    }

    //~ Accessor Methods ======================================

    public Long getId() {
        return id;
    }

    @SuppressWarnings("unused")
    private void setId(Long id) {
        this.id = id;
    }

     @SuppressWarnings("unused")
    private String getUuid() {
        return uuid;
    }

    @SuppressWarnings("unused")
    private void setUuid(String uuid) {
        this.uuid = uuid;
     }
}

只需为所有实体扩展ModelBase。这种技术的优点是一旦创建了对象就会分配uuid。但是我们仍然有一个指定的id,我们可以在我们的应用程序代码中使用它来查询特定对基本上,除了比较目的之外,uuid字段从未在我们的应用程序代码中使用或考虑过。像魅力一样。