Composite Primary Keys with Foreign Key Relationships in JPA and Hibernate

Introduction

Working with composite primary keys in Java Persistence API (JPA) and Hibernate can be tricky—especially when part of the composite key also acts as a foreign key to another entity. Many developers encounter issues when trying to map these relationships, such as only part of the foreign key being correctly mapped or schema generation tools failing to generate the appropriate constraints.

In this article, we’ll walk through how to correctly map a composite primary key that includes a foreign key reference, resolve common errors, and ensure your schema and entities are working together as expected.


✅ Scenario: Composite Key with Foreign Key Reference

Let’s say you have a parent-child relationship like this:

  • ParentEntity has a composite primary key made of partOne and partTwo.
  • ChildEntity has a composite key that includes both parts of the parent key (as a foreign key) and a third field to uniquely identify itself.

The goal is to:

  • Map both entities properly.
  • Make sure the foreign key constraint is correctly generated on both parts.
  • Avoid common Hibernate mapping exceptions.

🔧 Step-by-Step Solution

1. Define the Composite Key Class for the Parent Entity

@Embeddable
public class ParentId implements Serializable {
    private Long partOne;
    private Long partTwo;

    // equals() and hashCode() are required!
}

2. Define the ParentEntity with @IdClass

@Entity
@IdClass(ParentId.class)
public class ParentEntity {
    @Id
    private Long partOne;

    @Id
    private Long partTwo;

    @OneToMany(mappedBy = "parent")
    private List<ChildEntity> children = new ArrayList<>();
}

💡 Note: We’re using @IdClass here, which allows you to declare multiple @Id fields directly in the entity.


3. Define the Composite Key Class for the Child Entity

@Embeddable
public class ChildId implements Serializable {
    private Long parentPartOne;
    private Long parentPartTwo;
    private Long sequence; // The child-specific part of the composite key

    // equals() and hashCode() are mandatory
}

4. Define the ChildEntity with @EmbeddedId and @ManyToOne + @JoinColumns

@Entity
public class ChildEntity {
    @EmbeddedId
    private ChildId id;

    @ManyToOne
    @JoinColumns({
        @JoinColumn(name = "parentPartOne", referencedColumnName = "partOne", insertable = false, updatable = false),
        @JoinColumn(name = "parentPartTwo", referencedColumnName = "partTwo", insertable = false, updatable = false)
    })
    private ParentEntity parent;

    // Additional fields and logic
}

⚠️ Important: Make sure the column names in @JoinColumns match the ones in both the entity and the database schema. The insertable = false, updatable = false ensures Hibernate doesn’t try to update those manually—they are part of the key.


❌ Common Pitfalls

🚫 Only One Foreign Key Is Recognized

If Hibernate maps only one of the foreign key columns:

  • Double-check the spelling of referencedColumnName.
  • Confirm both columns are marked as @Id in the parent entity.
  • Ensure both fields exist in the child key class.

🚫 Schema Generation Misses Foreign Key Constraint

Hibernate’s automatic schema generation (hibernate.hbm2ddl.auto=create) sometimes fails to generate composite foreign key constraints correctly. To fix this:

  • Use a schema management tool like Flyway or Liquibase.
  • Alternatively, manually add the constraint in your DBMS.

🧪 Testing Tips

  1. Enable SQL logging (hibernate.show_sql=true) to confirm the generated DDL.
  2. Use tools like DBeaver or pgAdmin to visually inspect foreign key constraints.
  3. Write integration tests that check for integrity violations.

✅ Summary

Mapping a composite key that includes a foreign key in JPA and Hibernate requires:

  • Accurate definition of key classes with equals() and hashCode().
  • Proper use of @IdClass or @EmbeddedId depending on your strategy.
  • Correct use of @JoinColumns to ensure all referenced fields are mapped.
  • Manual schema inspection or external tools when automatic schema generation falls short.

Done right, this pattern enables powerful relationships and enforces data integrity across complex domain models.

This article is inspired by real-world challenges we tackle in our projects. If you're looking for expert solutions or need a team to bring your idea to life,

Let's talk!

    Please fill your details, and we will contact you back

      Please fill your details, and we will contact you back