Part 7 – Composite Keys, Embedded IDs and Primary Key Design


Introduction

Primary key design is one of the most important decisions in database design.

A poorly chosen key can cause:

❌ Complex joins

❌ Difficult JPA mappings

❌ Large indexes

❌ Slow queries

❌ Complicated APIs

❌ Difficult migrations

Many legacy Oracle systems use composite keys extensively, while modern applications increasingly use surrogate keys or UUIDs.

This article covers:

✅ Primary key strategies

✅ Composite keys

✅ EmbeddedId

✅ IdClass

✅ Natural keys

✅ Surrogate keys

✅ Shared primary keys

✅ UUID keys

✅ JPA best practices


What is a Primary Key?

A primary key uniquely identifies a row.

EMPLOYEE
---------
EMP_ID
NAME
EMAIL
PRIMARY KEY (EMP_ID)

Properties:

  • Unique
  • Not Null
  • Stable
  • Immutable

Primary Key Strategies

StrategyExampleRecommendation
SequenceEMP_IDExcellent
IdentityIDENTITYGood
UUIDUUIDGood
CompositeORDER_ID + ITEM_IDSituational
Natural KeyEMAILAvoid

Surrogate Keys

Generated identifiers.

EMP_ID NUMBER(19)

Advantages:

✅ Small indexes

✅ Fast joins

✅ Easy JPA mappings

✅ Simple APIs


Natural Keys

Example:

EMAIL VARCHAR2(200)

Problems:

  • Email changes.
  • Business rules change.
  • Larger indexes.

Avoid:

PRIMARY KEY (EMAIL)

Sequence Keys

Oracle recommendation:

CREATE SEQUENCE EMP_SEQ;

JPA:

@Id
@GeneratedValue(
    strategy =
        GenerationType.SEQUENCE,
    generator = "emp_seq")

UUID Keys

Java:

UUID id = UUID.randomUUID();

Database:

EMP_ID RAW(16)

Advantages:

  • Globally unique.
  • Distributed systems.
  • Microservices.

Disadvantages:

  • Larger indexes.
  • Random insertion.

When to Use UUID

ScenarioRecommendation
MonolithSequence
BankingSequence
ERPSequence
MicroservicesUUID
Event-driven systemsUUID

Composite Keys

A composite key consists of multiple columns.

Example:

ORDER_ITEM
----------
ORDER_ID
PRODUCT_ID
QUANTITY

Primary key:

PRIMARY KEY
(
    ORDER_ID,
    PRODUCT_ID
)

When Composite Keys Are Appropriate

Good examples:

  • Order Items
  • Student Courses
  • User Roles
  • Product Categories

When to Avoid Composite Keys

Avoid:

❌ Large tables

❌ Frequently joined entities

❌ REST APIs

❌ Microservices


JPA Composite Key Approaches

JPA supports:

  1. EmbeddedId
  2. IdClass

EmbeddedId

Recommended approach.


Step 1: Create Key Class

@Embeddable
public class OrderItemId
        implements Serializable {

    private Long orderId;

    private Long productId;
}

Requirements

The key class must:

✅ Implement Serializable

✅ Have default constructor

✅ Override equals()

✅ Override hashCode()


Step 2: Entity

@Entity
public class OrderItem {

    @EmbeddedId
    private OrderItemId id;

    private Integer quantity;
}

Database

ORDER_ITEM
----------
ORDER_ID
PRODUCT_ID
QUANTITY

Accessing Fields

orderItem.getId()
         .getOrderId();

IdClass

Alternative approach.


Key Class

public class OrderItemId
        implements Serializable {

    private Long orderId;

    private Long productId;
}

Entity

@Entity
@IdClass(OrderItemId.class)
public class OrderItem {

    @Id
    private Long orderId;

    @Id
    private Long productId;
}

EmbeddedId vs IdClass

FeatureEmbeddedIdIdClass
EncapsulationYesNo
Cleaner codeYesModerate
ReusableYesLimited
RecommendedYesLess common

Shared Primary Keys

Example:

Employee
     |
     1
     |
     1
Passport

Database

PASSPORT
---------
EMP_ID PK
EMP_ID FK

Entity

@Entity
public class Passport {

    @Id
    private Long id;

    @MapsId
    @OneToOne
    private Employee employee;
}

@MapsId

Allows:

  • Shared primary key.
  • Parent-child identity.

Composite Key with Relationships

Example:

@Embeddable
public class EnrollmentId {

    private Long studentId;

    private Long courseId;
}

Entity:

@Entity
public class Enrollment {

    @EmbeddedId
    private EnrollmentId id;

    @ManyToOne
    @MapsId("studentId")
    private Student student;

    @ManyToOne
    @MapsId("courseId")
    private Course course;
}

Junction Entity

Instead of:

@ManyToMany
private Set<Role> roles;

Prefer:

UserRole

Example

@Entity
public class UserRole {

    @EmbeddedId
    private UserRoleId id;
}

Advantages:

  • Additional columns.
  • Audit fields.
  • Better control.

Equals and HashCode

Mandatory for key classes.

Example:

@Override
public boolean equals(Object o) {
}
@Override
public int hashCode() {
}

Why Important?

Hibernate uses:

  • HashSet
  • HashMap
  • Persistence context

Incorrect implementations cause:

  • Duplicate entities.
  • Cache issues.
  • Unexpected behavior.

UUID as Primary Key

@Id
private UUID id;

Oracle:

EMP_ID RAW(16)

Hibernate:

@GeneratedValue
private UUID id;

Advantages

✅ Global uniqueness.

✅ No sequence calls.

✅ Suitable for distributed systems.


Disadvantages

❌ Larger indexes.

❌ More storage.

❌ Random insertion.


REST API Impact

Composite key:

/orders/10/products/20

Surrogate key:

/order-items/100

Simpler APIs.


Caching Considerations

Composite keys:

(10,20)

Surrogate keys:

100

Smaller cache entries.


Migration Challenges

Changing primary keys later is difficult.

Impacts:

  • Foreign keys
  • APIs
  • JPA mappings
  • Caches

Choose carefully.


Recommended Strategy

Table TypeRecommendation
Master tablesSequence
Reference dataSequence
Large tablesSequence
MicroservicesUUID
Junction tablesComposite
Relationship tablesComposite

Example Enterprise Model

EMPLOYEE
---------
EMP_ID

ORDER
------
ORDER_ID

ORDER_ITEM
----------
ORDER_ID
PRODUCT_ID

Common Mistakes

Business Key as Primary Key

EMAIL

Bad.


Large Composite Keys

COUNTRY
STATE
CITY
ZIP

Bad.


Missing equals/hashCode

Causes bugs.


UUID Stored as VARCHAR(100)

Wasteful.


Best Practices

✅ Prefer surrogate keys.

✅ Use sequences in Oracle.

✅ Use EmbeddedId.

✅ Keep keys immutable.

✅ Use UUID for distributed systems.

✅ Use composite keys only for relationship tables.

✅ Implement equals and hashCode.

✅ Use RAW(16) for UUID storage.


Interview Questions

Difference between EmbeddedId and IdClass?

EmbeddedId encapsulates key fields.

IdClass duplicates fields.


When should composite keys be used?

Junction tables.


Why avoid natural keys?

Business values change.


Why use RAW(16) for UUID?

Smaller indexes.


What does @MapsId do?

Maps relationship to primary key.


Summary

This article covered:

✅ Surrogate keys

✅ Natural keys

✅ Composite keys

✅ EmbeddedId

✅ IdClass

✅ UUID keys

✅ Shared keys

✅ MapsId

✅ Key design


Next Article

Part 8 – Lazy Loading, N+1 Queries and Hibernate Performance Tuning

Topics:

  • Lazy loading
  • Eager loading
  • N+1 problem
  • Join fetch
  • Entity graphs
  • DTO projections
  • Batch fetching
  • Second-level cache
  • Query optimization
  • Hibernate statistics

Leave a Reply

Your email address will not be published. Required fields are marked *