Jin Kwon
Jin Kwon

Reputation: 21997

How should I override equals/hashCode in entities with composite primary keys?

I have a table with a composite primary keys including it's own and a FK from other table.

I decided not to use an @IdClass nor @EmbeddedId.

class SomeEntity {

    @Id
    private String someId;

    @Id
    @ManyToOne(optional = false)
    @JoinColumn(name = ...)
    private Other other

    // other mappings here
}

And Hibernate warns these.

WARN 69752 ... : HHH000038: Composite-id class does not override equals(): ....Some
WARN 69752 ... : HHH000039: Composite-id class does not override hashCode(): ....Some

How can(should) I implement these two methods?

Can(should) I include only these two fields? What about other fields?

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Some that = (Some) o;
        return Objects.equals(someId, that.someId) &&
                Objects.equals(other, that.other);
    }

    @Override
    public int hashCode() {
        return Objects.hash(someId, other);
    }

Upvotes: 6

Views: 5093

Answers (1)

SternK
SternK

Reputation: 13101

I decided not to use an @IdClass nor @EmbeddedId.

I have to say that this decision has two drawbacks:

  1. This approach is not allowed by JPA specification, so it is hibernate specific solution. (See this)
  2. There’s no separation between the entity instance and the actual identifier. To query this entity, an instance of the entity itself must be supplied to the persistence context. (See this)

Imagine, you have the following entity:

@Entity
@Table(name = "TST_FIRST_ENTITY_EHC")
public class FirstEntity implements Serializable 
{
   @Id
   @Column(name = "fst_id")
   private Long id;
   
   @NaturalId
   @Column(name = "fst_code")
   private String code;
   
   @Id
   @ManyToOne(optional = false)
   @JoinColumn(name = "fst_sec_id")
   private SecondEntity secondEntity;
   
   public FirstEntity()
   {
   }
   
   public FirstEntity(Long id, Long secId)
   {
      this.id = id;
      secondEntity = new SecondEntity();
      secondEntity.setId(secId);
   }
   // ...
}

To find this entity by PK you should write something like this:

FirstEntity item = session.find(FirstEntity.class, new FirstEntity(1L, 2L));

As for me, it looks quite awkward.

As for your main question, you can use any set of fields that identify your entity. Imagine, that for the above entity you have the following DDL SQL:

create table TST_FIRST_ENTITY_EHC
(
   fst_id bigint,
   fst_sec_id bigint,
   fst_code varchar(25),
   fst_value varchar(500),
   
   primary key (fst_id, fst_sec_id),
   unique(fst_code),
   foreign key (fst_sec_id) REFERENCES TST_SECOND_ENTITY_EHC(sec_id)
);

You can implement equals and hashCode of your entity based on the id, secondEntity.id fields or based on the code field. The approach based on the natural-id or business-key is preferable because if you use database id generation you can not know the actual id values before the JPA transaction is committed. (See this).

Upvotes: 1

Related Questions