This repository demonstrates five approaches for modeling inheritance and polymorphic associations in relational databases using JPA/Hibernate:
- Single Table Inheritance
- Join Table (Joined) Inheritance
- Table Per Class Inheritance
- Any Discriminator Approach
- One-to-One with Any Discriminator Approach
Approach | Flexibility | Referential Integrity | Query Performance | Insert Performance | Schema Simplicity | Best Use Case |
---|---|---|---|---|---|---|
Single Table Inheritance | Low | Yes | Fastest – 230 µs | Moderate – 537 µs | Simple | Small hierarchies with many shared fields |
Join Table Inheritance | Medium | Yes | Slow – 266 µs (joins) | Slowest – 791 µs | Moderate | Complex hierarchies with distinct fields |
Table Per Class | Low | No | Fast – 253 µs (UNION) | Fastest – 458 µs | Fragmented | Independent subclasses, rarely queried together |
Any Discriminator | High | No (app-level only) | Fast – 237 µs | Moderate – 556 µs | Simple generic | Generic relationships, activity feeds |
One-to-One + Any Discriminator | Medium | Partial (possible orphan) | Fast – 239 µs | Slow – 991 µs | Simple | Polymorphic one-to-one links with partial integrity |
All entities in a hierarchy are stored in a single table, distinguished by a discriminator column.
- Single Table: One table holds all subclass data.
- Discriminator Column: Identifies which subclass a row represents.
- Nullable Columns: Non-shared attributes appear as nullable for unrelated types.
- Simplifies schema (one table only).
- Easy to query across hierarchy.
- Good performance for small/medium hierarchies.
- Lots of null columns if subclasses differ heavily.
- Schema can become “wide” and messy.
- Harder to evolve when subclasses diverge a lot.
entity_relationship table:
relationship table:
Each subclass gets its own table. The base class table holds shared attributes, and subclass tables hold specific ones.
- Joined Tables: Queries reconstruct full entities with joins.
- No Null Columns: Each table has only its relevant attributes.
- Clean separation of subclass fields.
- No wasted space.
- Referential integrity is preserved between base and subclass.
- Requires joins for polymorphic queries.
- More complex queries (can impact performance).
entity_relationship table:
relationship table:
Each concrete class has its own full table, including inherited fields. No shared base table.
- Independent Tables: No foreign keys between subclass tables.
- Polymorphic Queries: Hibernate generates
UNION
across tables.
- Each table is self-contained.
- Good if subclasses are rarely queried together.
- No referential integrity at the DB level for the hierarchy.
- Polymorphic queries can be slow (use UNION).
- Duplicate schema between tables.
entity_relationship table:
There is no relationship table in this approach.
A generic relationship table stores a type
and id
pair to reference arbitrary entities. Uses Hibernate’s
@Any
/ @AnyDiscriminator
.
- Flexible: Can link to any entity type dynamically.
- No Foreign Keys: Database does not enforce integrity. Hibernate resolves references.
- Maximum flexibility for polymorphic relationships.
- Reduces need for multiple relationship tables.
- Useful for activity feeds, audit logs, metadata, etc.
- No DB-level referential integrity (orphaned references possible).
- Harder to enforce constraints.
- Queries require extra care (joins by type/id).
entity_relationship table:
There is no relationship table in this approach.
A refinement of the Any Discriminator pattern, but applied in a one-to-one relationship context.
- One-to-One Polymorphism: Each row in an entity (e.g.
User
,Project
,Task
,Organization
) links to exactly one row in theRelationship
table. - Discriminator Column: The
Relationship
table holds a discriminator (entity_type
) and target key (entity_id
) to resolve back to the owner. - Referential Integrity: Unlike the pure Any Discriminator approach, this design partially enforces integrity
via foreign keys (
relationship_id
) from entity tables into theRelationship
table.
- Retains flexibility of
@Any
while gaining partial integrity guarantees. - DB-level foreign key ensures each entity points to a valid
Relationship
entry. - Clear one-to-one semantics: each entity has exactly one
Relationship
.
- Partial referential integrity: The
Relationship
table can still contain dangling entries if the owning entity is deleted without cascading cleanup. - More rigid than the pure Any approach (not many-to-any).
- Slightly more tables and joins involved compared to simpler inheritance.
entity_relationship table:
The following results were obtained using JMH benchmarks on each inheritance/association strategy.
Approach | Operation | Avg Time (µs/op) | Error (µs/op) |
---|---|---|---|
Single Table | fetchRandomOrg | 230.283 | ±65.968 |
insertNewOrg | 536.638 | ±358.074 | |
Join Table | fetchRandomOrg | 265.614 | ±262.397 |
insertNewOrg | 790.703 | ±886.336 | |
Table Per Class | fetchRandomOrg | 252.662 | ±28.145 |
insertNewOrg | 457.687 | ±202.391 | |
Any Discriminator | fetchRandomOrg | 236.679 | ±78.722 |
insertNewOrg | 555.785 | ±129.829 | |
One-to-One + Any Discriminator | fetchRandomOrg | 238.737 | ±23.863 |
insertNewOrg | 990.658 | ±227.675 |
- Fetch performance is comparable across all approaches (~230–265 µs/op).
- Insert performance varies more significantly:
- Fastest inserts: Table Per Class (~457 µs/op).
- Slowest inserts: One-to-One + Any Discriminator (~990 µs/op), likely due to extra join and referential checks.
- Single Table provides a strong balance: good fetch speed and moderate insert cost.
- Join Table has the highest variance in inserts due to multiple joins.
- Any Discriminator is competitive in both fetch and insert, at the cost of referential integrity.
- One-to-One + Any Discriminator provides partial referential integrity but may leave orphaned relationships if not properly cascaded.